<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
  <meta name="theme-color" content="#222">
  <meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/safari-pinned-tab.svg" color="#222">
  <link rel="stylesheet" href="/css/main.css">
  <link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib/pace/pace-theme-minimal.min.css">
  <script src="/lib/pace/pace.min.js"></script>
  <script id="hexo-configurations">
    var NexT = window.NexT ||
    {};
    var CONFIG = {
      "hostname": "cuiqingcai.com",
      "root": "/",
      "scheme": "Pisces",
      "version": "7.8.0",
      "exturl": false,
      "sidebar":
      {
        "position": "right",
        "width": 360,
        "display": "post",
        "padding": 18,
        "offset": 12,
        "onmobile": false,
        "widgets": [
          {
            "type": "image",
            "name": "阿布云",
            "enable": false,
            "url": "https://www.abuyun.com/http-proxy/introduce.html",
            "src": "https://qiniu.cuiqingcai.com/88au8.jpg",
            "width": "100%"
      },
          {
            "type": "image",
            "name": "天验",
            "enable": true,
            "url": "https://tutorial.lengyue.video/?coupon=12ef4b1a-a3db-11ea-bb37-0242ac130002_cqx_850",
            "src": "https://qiniu.cuiqingcai.com/bco2a.png",
            "width": "100%"
      },
          {
            "type": "image",
            "name": "华为云",
            "enable": false,
            "url": "https://activity.huaweicloud.com/2020_618_promotion/index.html?bpName=5f9f98a29e2c40b780c1793086f29fe2&bindType=1&salesID=wangyubei",
            "src": "https://qiniu.cuiqingcai.com/y42ik.jpg",
            "width": "100%"
      },
          {
            "type": "image",
            "name": "张小鸡",
            "enable": false,
            "url": "http://www.zxiaoji.com/",
            "src": "https://qiniu.cuiqingcai.com/fm72f.png",
            "width": "100%"
      },
          {
            "type": "image",
            "name": "Luminati",
            "src": "https://qiniu.cuiqingcai.com/ikkq9.jpg",
            "url": "https://luminati-china.io/?affiliate=ref_5fbbaaa9647883f5c6f77095",
            "width": "100%",
            "enable": false
      },
          {
            "type": "image",
            "name": "IPIDEA",
            "url": "http://www.ipidea.net/?utm-source=cqc&utm-keyword=?cqc",
            "src": "https://qiniu.cuiqingcai.com/0ywun.png",
            "width": "100%",
            "enable": true
      },
          {
            "type": "tags",
            "name": "标签云",
            "enable": true
      },
          {
            "type": "categories",
            "name": "分类",
            "enable": true
      },
          {
            "type": "friends",
            "name": "友情链接",
            "enable": true
      },
          {
            "type": "hot",
            "name": "猜你喜欢",
            "enable": true
      }]
      },
      "copycode":
      {
        "enable": true,
        "show_result": true,
        "style": "mac"
      },
      "back2top":
      {
        "enable": true,
        "sidebar": false,
        "scrollpercent": true
      },
      "bookmark":
      {
        "enable": false,
        "color": "#222",
        "save": "auto"
      },
      "fancybox": false,
      "mediumzoom": false,
      "lazyload": false,
      "pangu": true,
      "comments":
      {
        "style": "tabs",
        "active": "gitalk",
        "storage": true,
        "lazyload": false,
        "nav": null,
        "activeClass": "gitalk"
      },
      "algolia":
      {
        "hits":
        {
          "per_page": 10
        },
        "labels":
        {
          "input_placeholder": "Search for Posts",
          "hits_empty": "We didn't find any results for the search: ${query}",
          "hits_stats": "${hits} results found in ${time} ms"
        }
      },
      "localsearch":
      {
        "enable": true,
        "trigger": "auto",
        "top_n_per_article": 10,
        "unescape": false,
        "preload": false
      },
      "motion":
      {
        "enable": false,
        "async": false,
        "transition":
        {
          "post_block": "bounceDownIn",
          "post_header": "slideDownIn",
          "post_body": "slideDownIn",
          "coll_header": "slideLeftIn",
          "sidebar": "slideUpIn"
        }
      },
      "path": "search.xml"
    };

  </script>
  <meta name="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
  <meta property="og:type" content="website">
  <meta property="og:title" content="静觅">
  <meta property="og:url" content="https://cuiqingcai.com/page/13/index.html">
  <meta property="og:site_name" content="静觅">
  <meta property="og:description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
  <meta property="og:locale" content="zh_CN">
  <meta property="article:author" content="崔庆才">
  <meta property="article:tag" content="崔庆才">
  <meta property="article:tag" content="静觅">
  <meta property="article:tag" content="PHP">
  <meta property="article:tag" content="Java">
  <meta property="article:tag" content="Python">
  <meta property="article:tag" content="Spider">
  <meta property="article:tag" content="爬虫">
  <meta property="article:tag" content="Web">
  <meta property="article:tag" content="Kubernetes">
  <meta property="article:tag" content="深度学习">
  <meta property="article:tag" content="机器学习">
  <meta property="article:tag" content="数据分析">
  <meta property="article:tag" content="网络">
  <meta property="article:tag" content="IT">
  <meta property="article:tag" content="技术">
  <meta property="article:tag" content="博客">
  <meta name="twitter:card" content="summary">
  <link rel="canonical" href="https://cuiqingcai.com/page/13/">
  <script id="page-configurations">
    // https://hexo.io/docs/variables.html
    CONFIG.page = {
      sidebar: "",
      isHome: true,
      isPost: false,
      lang: 'zh-CN'
    };

  </script>
  <title>静觅丨崔庆才的个人站点</title>
  <meta name="google-site-verification" content="p_bIcnvirkFzG2dYKuNDivKD8-STet5W7D-01woA2fc" />
  <noscript>
    <style>
      .use-motion .brand,
      .use-motion .menu-item,
      .sidebar-inner,
      .use-motion .post-block,
      .use-motion .pagination,
      .use-motion .comments,
      .use-motion .post-header,
      .use-motion .post-body,
      .use-motion .collection-header
      {
        opacity: initial;
      }

      .use-motion .site-title,
      .use-motion .site-subtitle
      {
        opacity: initial;
        top: initial;
      }

      .use-motion .logo-line-before i
      {
        left: initial;
      }

      .use-motion .logo-line-after i
      {
        right: initial;
      }

    </style>
  </noscript>
  <link rel="alternate" href="/atom.xml" title="静觅" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container">
    <div class="headband"></div>
    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner">
        <div class="site-brand-container">
          <div class="site-nav-toggle">
            <div class="toggle" aria-label="切换导航栏">
              <span class="toggle-line toggle-line-first"></span>
              <span class="toggle-line toggle-line-middle"></span>
              <span class="toggle-line toggle-line-last"></span>
            </div>
          </div>
          <div class="site-meta">
            <a href="/" class="brand" rel="start">
              <span class="logo-line-before"><i></i></span>
              <h1 class="site-title">静觅 <span class="site-subtitle"> 崔庆才的个人站点 </span>
              </h1>
              <span class="logo-line-after"><i></i></span>
            </a>
          </div>
          <div class="site-nav-right">
            <div class="toggle popup-trigger">
              <i class="fa fa-search fa-fw fa-lg"></i>
            </div>
          </div>
        </div>
        <nav class="site-nav">
          <ul id="menu" class="main-menu menu">
            <li class="menu-item menu-item-home">
              <a href="/" rel="section">首页</a>
            </li>
            <li class="menu-item menu-item-archives">
              <a href="/archives/" rel="section">文章列表</a>
            </li>
            <li class="menu-item menu-item-tags">
              <a href="/tags/" rel="section">文章标签</a>
            </li>
            <li class="menu-item menu-item-categories">
              <a href="/categories/" rel="section">文章分类</a>
            </li>
            <li class="menu-item menu-item-about">
              <a href="/about/" rel="section">关于博主</a>
            </li>
            <li class="menu-item menu-item-message">
              <a href="/message/" rel="section">给我留言</a>
            </li>
            <li class="menu-item menu-item-search">
              <a role="button" class="popup-trigger">搜索 </a>
            </li>
          </ul>
        </nav>
        <div class="search-pop-overlay">
          <div class="popup search-popup">
            <div class="search-header">
              <span class="search-icon">
                <i class="fa fa-search"></i>
              </span>
              <div class="search-input-container">
                <input autocomplete="off" autocapitalize="off" placeholder="搜索..." spellcheck="false" type="search" class="search-input">
              </div>
              <span class="popup-btn-close">
                <i class="fa fa-times-circle"></i>
              </span>
            </div>
            <div id="search-result">
              <div id="no-result">
                <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
              </div>
            </div>
          </div>
        </div>
      </div>
    </header>
    <div class="back-to-top">
      <i class="fa fa-arrow-up"></i>
      <span>0%</span>
    </div>
    <div class="reading-progress-bar"></div>
    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div class="content index posts-expand">
            <div class="carousel">
              <div id="wowslider-container">
                <div class="ws_images">
                  <ul>
                    <li><a target="_blank" href="https://cuiqingcai.com/5052.html"><img title="Python3网络爬虫开发实战教程" src="https://qiniu.cuiqingcai.com/ipy96.jpg" /></a></li>
                    <li><a target="_blank" href="https://t.lagou.com/fRCBRsRCSN6FA"><img title="52讲轻松搞定网络爬虫" src="https://qiniu.cuiqingcai.com/fqq5e.png" /></a></li>
                    <li><a target="_blank" href="https://brightdata.grsm.io/cuiqingcai"><img title="亮网络解锁器" src="https://qiniu.cuiqingcai.com/6qnb7.png" /></a></li>
                    <li><a target="_blank" href="https://cuiqingcai.com/4320.html"><img title="Python3网络爬虫开发视频教程" src="https://qiniu.cuiqingcai.com/bjrny.jpg" /></a></li>
                    <li><a target="_blank" href="https://cuiqingcai.com/5094.html"><img title="爬虫代理哪家强？十大付费代理详细对比评测出炉！" src="https://qiniu.cuiqingcai.com/nifs6.jpg" /></a></li>
                  </ul>
                </div>
                <div class="ws_thumbs">
                  <div>
                    <a target="_blank" href="#"><img src="https://qiniu.cuiqingcai.com/ipy96.jpg" /></a>
                    <a target="_blank" href="#"><img src="https://qiniu.cuiqingcai.com/fqq5e.png" /></a>
                    <a target="_blank" href="#"><img src="https://qiniu.cuiqingcai.com/6qnb7.png" /></a>
                    <a target="_blank" href="#"><img src="https://qiniu.cuiqingcai.com/bjrny.jpg" /></a>
                    <a target="_blank" href="#"><img src="https://qiniu.cuiqingcai.com/nifs6.jpg" /></a>
                  </div>
                </div>
                <div class="ws_shadow"></div>
              </div>
            </div>
            <link rel="stylesheet" href="/lib/wowslide/slide.css">
            <script src="/lib/wowslide/jquery.min.js"></script>
            <script src="/lib/wowslide/slider.js"></script>
            <script>
              jQuery("#wowslider-container").wowSlider(
              {
                effect: "cube",
                prev: "",
                next: "",
                duration: 20 * 100,
                delay: 20 * 100,
                width: 716,
                height: 297,
                autoPlay: true,
                playPause: true,
                stopOnHover: false,
                loop: false,
                bullets: 0,
                caption: true,
                captionEffect: "slide",
                controls: true,
                onBeforeStep: 0,
                images: 0
              });

            </script>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/7035.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> Python <i class="label-arrow"></i>
                  </a>
                  <a href="/7035.html" class="post-title-link" itemprop="url">[Python3网络爬虫开发实战] 8.1-图形验证码的识别</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>本节我们首先来尝试识别最简单的一种验证码，图形验证码，这种验证码出现的最早，现在也很常见，一般是四位字母或者数字组成的，例如中国知网的注册页面就有类似的验证码，链接为：<a href="http://my.cnki.net/elibregister/commonRegister.aspx" target="_blank" rel="noopener">http://my.cnki.net/elibregister/commonRegister.aspx</a>，页面如图 8-1 所示： <img src="https://qiniu.cuiqingcai.com/2019-08-02-054718.png" alt=""> 图 8-1 知网注册页面 表单的最后一项就是图形验证码，我们必须完全输入正确图中的字符才可以完成注册。</p>
                  <h3 id="1-本节目标"><a href="#1-本节目标" class="headerlink" title="1. 本节目标"></a>1. 本节目标</h3>
                  <p>本节我们就以知网的验证码为例，讲解一下利用 OCR 技术识别此种图形验证码的方法。</p>
                  <h3 id="2-准备工作"><a href="#2-准备工作" class="headerlink" title="2. 准备工作"></a>2. 准备工作</h3>
                  <p>识别图形验证码需要的库有 Tesserocr，如果没有安装可以参考第一章的安装说明。</p>
                  <h3 id="3-获取验证码"><a href="#3-获取验证码" class="headerlink" title="3. 获取验证码"></a>3. 获取验证码</h3>
                  <p>为了便于实验，我们先将验证码的图片保存到本地，以供测试。 打开开发者工具，找到验证码元素，可以看到这是一张图片，它的 src 属性是 CheckCode.aspx，在这里我们直接将这个链接打开：<a href="http://my.cnki.net/elibregister/CheckCode.aspx" target="_blank" rel="noopener">http://my.cnki.net/elibregister/CheckCode.aspx</a>，就可以看到一个验证码，直接右键保存下来即可，将名称命名为 code.jpg，如图 8-2 所示： <img src="https://qiniu.cuiqingcai.com/2019-08-02-054745.jpg" alt=""> 图 8-2 验证码 这样我们就可以得到一张验证码图片供下面测试识别使用了。</p>
                  <h3 id="4-识别测试"><a href="#4-识别测试" class="headerlink" title="4. 识别测试"></a>4. 识别测试</h3>
                  <p>接下来我们新建一个项目，将验证码图片放到项目根目录下，用 Tesserocr 库来识别一下该验证码试试，代码如下：</p>
                  <figure class="highlight processing">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">import</span> tesserocr</span><br><span class="line">from PIL <span class="keyword">import</span> Image</span><br><span class="line"></span><br><span class="line"><span class="built_in">image</span> = Image.<span class="built_in">open</span>(<span class="string">'code.jpg'</span>)</span><br><span class="line">result = tesserocr.image_to_text(<span class="built_in">image</span>)</span><br><span class="line"><span class="built_in">print</span>(result)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>在这里我们首先新建了一个 Image 对象，然后调用了 Tesserocr 的 image_to_text() 方法，传入该 Image 对象即可完成识别，实现过程非常简单，识别结果如下：</p>
                  <figure class="highlight plain">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">JR42</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>另外 Tesserocr 还有一个更加简单的方法直接将图片文件转为字符串可以达到同样的效果，代码如下：</p>
                  <figure class="highlight lisp">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import tesserocr</span><br><span class="line">print(<span class="name">tesserocr</span>.file_to_text('image.png'))</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>不过经测试此种方法的识别效果不如上一种方法好。</p>
                  <h3 id="5-验证码处理"><a href="#5-验证码处理" class="headerlink" title="5. 验证码处理"></a>5. 验证码处理</h3>
                  <p>如上的图片识别基本没有难度，只是新建一个 Image 对象，然后调用 image_to_text() 方法即可得出图片的识别结果。 接下来我们换一个验证码试一下，命名为 code2.jpg，如图 8-3 所示： <img src="https://qiniu.cuiqingcai.com/2019-08-02-054722.jpg" alt=""> 图 8-3 验证码 重新用下面的代码测试一下：</p>
                  <figure class="highlight processing">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">import</span> tesserocr</span><br><span class="line">from PIL <span class="keyword">import</span> Image</span><br><span class="line"></span><br><span class="line"><span class="built_in">image</span> = Image.<span class="built_in">open</span>(<span class="string">'code2.jpg'</span>)</span><br><span class="line">result = tesserocr.image_to_text(<span class="built_in">image</span>)</span><br><span class="line"><span class="built_in">print</span>(result)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这时可以看到如下输出结果：</p>
                  <figure class="highlight ebnf">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="attribute">FFKT</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>发现这次识别和实际的结果有所偏差，这是因为验证码内的多余线条干扰了图片的识别。 对于这种情况，我们还需要做一下额外的处理，如转灰度、二值化等操作。 我们可以利用 Image 对象的 convert() 方法参数传入 L 即可将图片转化为灰度图像，代码如下：</p>
                  <figure class="highlight maxima">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="built_in">image</span> = <span class="built_in">image</span>.<span class="built_in">convert</span>('L')</span><br><span class="line"><span class="built_in">image</span>.<span class="built_in">show</span>()</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>传入 1 即可将图片进行二值化处理：</p>
                  <figure class="highlight maxima">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="built_in">image</span> = <span class="built_in">image</span>.<span class="built_in">convert</span>('<span class="number">1</span>')</span><br><span class="line"><span class="built_in">image</span>.<span class="built_in">show</span>()</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>另外我们还可以指定二值化的阈值，上面的方法采用的是默认阈值127，不过我们不能用原图直接转化，可以先转为灰度图像，然后再指定二值化阈值转化，代码如下：</p>
                  <figure class="highlight maxima">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="built_in">image</span> = <span class="built_in">image</span>.<span class="built_in">convert</span>('L')</span><br><span class="line">threshold = <span class="number">80</span></span><br><span class="line">table = []</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">256</span>):</span><br><span class="line">    <span class="keyword">if</span> i &lt; threshold:</span><br><span class="line">        table.<span class="built_in">append</span>(<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        table.<span class="built_in">append</span>(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">image</span> = <span class="built_in">image</span>.point(table, '<span class="number">1</span>')</span><br><span class="line"><span class="built_in">image</span>.<span class="built_in">show</span>()</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>在这里我们指定了一个变量 threshold 代表二值化阈值，阈值设置为 80，处理之后我们看一下结果，如图 8-4 所示： <img src="https://qiniu.cuiqingcai.com/2019-08-02-054728.jpg" alt=""> 图 8-4 处理结果 经过处理之后我们发现原来的验证码中的线条已经被去除了，而且整个验证码变得黑白分明，这时重新识别验证码，代码如下：</p>
                  <figure class="highlight processing">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">import</span> tesserocr</span><br><span class="line">from PIL <span class="keyword">import</span> Image</span><br><span class="line"></span><br><span class="line"><span class="built_in">image</span> = Image.<span class="built_in">open</span>(<span class="string">'code2.jpg'</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">image</span> = <span class="built_in">image</span>.convert(<span class="string">'L'</span>)</span><br><span class="line">threshold = <span class="number">127</span></span><br><span class="line">table = []</span><br><span class="line"><span class="keyword">for</span> i in range(<span class="number">256</span>):</span><br><span class="line">    <span class="keyword">if</span> i &lt; threshold:</span><br><span class="line">        table.<span class="built_in">append</span>(<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        table.<span class="built_in">append</span>(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">image</span> = <span class="built_in">image</span>.<span class="built_in">point</span>(table, <span class="string">'1'</span>)</span><br><span class="line">result = tesserocr.image_to_text(<span class="built_in">image</span>)</span><br><span class="line"><span class="built_in">print</span>(result)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>即可发现运行结果变成了：</p>
                  <figure class="highlight ebnf">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="attribute">PFRT</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>识别正确。 可见对于一些有干扰的图片，我们做一些灰度和二值化处理，会提高其识别正确率。</p>
                  <h3 id="6-本节代码"><a href="#6-本节代码" class="headerlink" title="6. 本节代码"></a>6. 本节代码</h3>
                  <p>本节代码地址为：<a href="https://github.com/Python3WebSpider/CrackImageCode" target="_blank" rel="noopener">https://github.com/Python3WebSpider/CrackImageCode</a>。</p>
                  <h3 id="7-结语"><a href="#7-结语" class="headerlink" title="7. 结语"></a>7. 结语</h3>
                  <p>本节我们了解了利用 Tesserocr 识别验证码的过程，对于简单的图形验证码我们可以直接用它来得到结果，如果要提高识别的准确度还可以对验证码图片做一下预处理。</p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-08-02 13:48:53" itemprop="dateCreated datePublished" datetime="2019-08-02T13:48:53+08:00">2019-08-02</time>
                </span>
                <span id="/7035.html" class="post-meta-item leancloud_visitors" data-flag-title="[Python3网络爬虫开发实战] 8.1-图形验证码的识别" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>2.2k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>2 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/7032.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> Python <i class="label-arrow"></i>
                  </a>
                  <a href="/7032.html" class="post-title-link" itemprop="url">[Python3网络爬虫开发实战] 8-验证码的识别</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>当今时代，许多网站为了反爬虫采用了各种各样的措施，其中之一便是使用验证码，随着技术的发展，验证码的花样也越来越多，最初可能是几个数字组合的简单的图形验证码，后来加入了英文字母和混淆曲线使得验证码更加复杂，有的网站还可能看到中文字符的验证码，使得识别愈发困难。 而后来 12306 验证码的出现又开辟了验证码的新纪元，用过 12306 的肯定多少为它的验证码头疼过，它需要我们去识别文字，然后再点击文字描述相符的图片，只有完全正确才可以验证通过。现在这种交互式验证码越来越多，如极验滑动验证码需要滑动拼合滑块才可以完成验证，点触的验证码需要完全点击正确的结果才可以完成验证，另外还有一些滑动宫格验证码，计算题验证码等等五花八门。 验证码变得越来越复杂，爬虫的工作也变得愈发艰难，有时候我们必须通过验证码的验证才可以访问页面，所以本章专门来针对验证码的识别做一下统一的讲解。 本章涉及的验证码有普通图形验证码、极验滑动验证码、点触验证码、微博宫格验证码，识别的方式和思路各有不同，了解了这几个验证码的识别方式之后，我们可以举一反三，用类似的方法识别其他类型的验证码。</p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-08-02 13:45:53" itemprop="dateCreated datePublished" datetime="2019-08-02T13:45:53+08:00">2019-08-02</time>
                </span>
                <span id="/7032.html" class="post-meta-item leancloud_visitors" data-flag-title="[Python3网络爬虫开发实战] 8-验证码的识别" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>474</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>1 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6978.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> 生活笔记 <i class="label-arrow"></i>
                  </a>
                  <a href="/6978.html" class="post-title-link" itemprop="url">人生数10载，你已度过几个10年？</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>人生一共就是由几个10年组成的，每个10年都有不同的经历，不同的心境，每个10年追求的东西都不一样。 鱼与熊掌不可兼得，在追求的过程中你总要有取舍，该取什么?该舍什么?</p>
                  <h2 id="10岁时"><a href="#10岁时" class="headerlink" title="10岁时"></a><strong><strong>10岁时</strong></strong></h2>
                  <p>应该不再计较家里给的零花钱多少，不和别人家的孩子比较穿名牌服装；少不更事，和人家比吃穿，还情有可原，年纪到了整数就该懂事了。</p>
                  <h2 id="20岁时"><a href="#20岁时" class="headerlink" title="20岁时"></a><strong><strong>20岁时</strong></strong></h2>
                  <p>不再计较自己的家庭出身，不再计较父母的职业十几岁时，会和别的孩子比较家庭出身，比爹娘官大官小，恨不得都投生帝王之家、将相之门，也是人之常情。 但如果到了“弱冠”之年，还弱不禁风，尚无自立之志，出身贫寒的还为家庭自卑，老觉得抬不起头来;出身富豪的还处处依靠父母，在家庭荫护下养尊处优，那就会一辈子都没出息。</p>
                  <h2 id="30岁时"><a href="#30岁时" class="headerlink" title="30岁时"></a><strong><strong>30岁时</strong></strong></h2>
                  <p>已成家立业，为父为母，有了几年家庭生活的经验。大丈夫该不再计较妻子的容貌，深知贤惠比美貌更重要，会过日子的媳妇比会打扮的媳妇更让人待见； 老婆该不再计较老公的身高，明白能力比身高更有作用，没有谋生能力的老公，纵然长成丈二金刚，还不如卖炊饼的大郎。</p>
                  <h2 id="40岁时"><a href="#40岁时" class="headerlink" title="40岁时"></a><strong><strong>40岁时</strong></strong></h2>
                  <p>不再计较别人的议论，谁爱说啥就说啥，自己想咋过就咋过。 人言可畏，想想上世纪30年代的阮玲玉还成如今的明星，一星期听不到他的绯闻轶事没人对他议论纷纷，他就急得火烧火燎的。 咱们虽然没有明星那高深道行，但不会再轻易被别人议论左右，这点本事应该有的，否则也对不起“不惑”这两个字啊。</p>
                  <h2 id="50岁时"><a href="#50岁时" class="headerlink" title="50岁时"></a><strong><strong>50岁时</strong></strong></h2>
                  <p>不再计较无处不在的不平之事，不再计较别人的成功对自己的压力，不再眼红他人的财。 半百之年，曾经沧海，阋人无数，见惯秋月春风，不再大惊小怪;历尽是非成败，不再愤愤不平看新贵飞扬跋扈，可不动声色;看大款挥金如土，也气定神闲，耐住性子。</p>
                  <h2 id="60岁时"><a href="#60岁时" class="headerlink" title="60岁时"></a><strong><strong>60岁时</strong></strong></h2>
                  <p>如果从政，该不再计较官大官小，退了体，官大官小一个样，都是退体干部。 如果经商，该不再计较利大利小，钱是挣不完的，再能花也是有限的，心态平和对自己身体有好处。 如果舞文弄墨，当不再计较文名大小，文坛座次。</p>
                  <h2 id="70岁时"><a href="#70岁时" class="headerlink" title="70岁时"></a><strong><strong>70岁时</strong></strong></h2>
                  <p>人到古稀，该不再计较的东西更多，看淡的事情更广。 年轻时争得你死我活的东西，现在只会淡然一笑。 中年时费尽心机格外计较的东西，如今看来已无关紧要。一生多少事，“都付笑谈中”。 这个岁数的老人，要有三样特别积极健康的身体、和谐的家庭、良好的名声。 人生在世，如果计较的东西太多，名利地位，金钱美色，样样都不肯放手，那就会如牛重负，活得很累。 反之，什么都不计较，什么都马马虎虎，什么都可以凑合，那也未免太对不起自己，活得没啥意思。 <strong><strong>人生智慧</strong></strong> <strong><strong>聪明的人，有生活智慧的人，会有所为，有所不为。他们只计较对自己最重要的东西，</strong></strong> <strong><strong>并且知道什么年龄该计较什么，不该计较什么，有取有舍，收放自如。</strong></strong></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/a385962416" class="author" itemprop="url" rel="index">a385962416</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-29 01:37:44" itemprop="dateCreated datePublished" datetime="2019-07-29T01:37:44+08:00">2019-07-29</time>
                </span>
                <span id="/6978.html" class="post-meta-item leancloud_visitors" data-flag-title="人生数10载，你已度过几个10年？" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>1.1k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>1 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6982.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> Python <i class="label-arrow"></i>
                  </a>
                  <a href="/6982.html" class="post-title-link" itemprop="url">你还在用 os.path？快来感受一下 pathlib 给你带来的便捷吧！</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <blockquote>
                    <p>相比常用的 os.path而言，pathlib 对于目录路径的操作更简介也更贴近 Pythonic。但是它不单纯是为了简化操作，还有更大的用途</p>
                  </blockquote>
                  <h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1>
                  <p>pathlib 是Python内置库，Python 文档给它的定义是 Object-oriented filesystem paths（面向对象的文件系统路径）。pathlib 提供表示文件系统路径的类，其语义适用于不同的操作系统。路径类在纯路径之间划分，纯路径提供纯粹的计算操作而没有I / O，以及具体路径，它继承纯路径但也提供I / O操作。 听起来有点绕？那就对了，毕竟这是直译过来的，但这并不影响我们喜爱它。 我们通过几个例子来了解它吧</p>
                  <h1 id="举个栗子"><a href="#举个栗子" class="headerlink" title="举个栗子"></a>举个栗子</h1>
                  <p>相对于 os 模块的 path 方法，Python3 标准库 pathlib 模块的 Path 对路径的操作会更简单。</p>
                  <h2 id="获取当前文件路径"><a href="#获取当前文件路径" class="headerlink" title="获取当前文件路径"></a>获取当前文件路径</h2>
                  <p>使用 os 模块时，有两种方法可以直接获取当前文件路径：</p>
                  <figure class="highlight stylus">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import os</span><br><span class="line"></span><br><span class="line">value1 = os<span class="selector-class">.path</span>.dirname(__file__)</span><br><span class="line">value2 = os.getcwd()</span><br><span class="line"><span class="function"><span class="title">print</span><span class="params">(value1)</span></span></span><br><span class="line"><span class="function"><span class="title">print</span><span class="params">(value2)</span></span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f1a91bbe4553?w=1408&amp;h=512&amp;f=png&amp;s=79211" alt=""> pathlib 获取当前文件路径应该怎么写呢？ 官方文档给出了建议 <a href="https://docs.python.org/3/library/pathlib.html#methods" target="_blank" rel="noopener">插眼传送</a> <img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f1e078abc837?w=863&amp;h=235&amp;f=png&amp;s=26250" alt=""> 动手试一试</p>
                  <figure class="highlight stylus">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import pathlib</span><br><span class="line"></span><br><span class="line">value1 = pathlib<span class="selector-class">.Path</span>.cwd()</span><br><span class="line"><span class="function"><span class="title">print</span><span class="params">(value1)</span></span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f1f1d4cb8275?w=1490&amp;h=501&amp;f=png&amp;s=71717" alt=""></p>
                  <h3 id="它是如何实现的"><a href="#它是如何实现的" class="headerlink" title="它是如何实现的"></a>它是如何实现的</h3>
                  <p>文档中有介绍，它以 os.getcwd() 的形式将路径返回。我们去源码中一探究竟（Pycharm 编辑器快捷键 ctrl+鼠标左键点击即可跟进指定对象） <img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f227b220c55b?w=983&amp;h=214&amp;f=png&amp;s=23854" alt=""> 原来它是对 os 模块中一些对象进行了封装，看 cwd 的注释： <code>Return a new path pointing to the current working directory</code> 意为：返回指向当前工作目录的新路径。 看起来也没什么特别的，但是为什么官方特意将它推出呢？</p>
                  <h3 id="其他的封装"><a href="#其他的封装" class="headerlink" title="其他的封装"></a>其他的封装</h3>
                  <p>pathlib 封装了很多的 os path ，文档中有写明，如：</p>
                  <figure class="highlight css">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"># 关系说明</span><br><span class="line"> <span class="selector-tag">os</span><span class="selector-class">.path</span><span class="selector-class">.expanduser</span>() <span class="selector-tag">--</span>&gt; <span class="selector-tag">pathlib</span><span class="selector-class">.Path</span><span class="selector-class">.home</span>()</span><br><span class="line"></span><br><span class="line"> <span class="selector-tag">os</span><span class="selector-class">.path</span><span class="selector-class">.expanduser</span>() <span class="selector-tag">--</span>&gt; <span class="selector-tag">pathlib</span><span class="selector-class">.Path</span><span class="selector-class">.expanduser</span>()</span><br><span class="line"></span><br><span class="line"> <span class="selector-tag">os</span><span class="selector-class">.stat</span>() <span class="selector-tag">--</span>&gt; <span class="selector-tag">pathlib</span><span class="selector-class">.Path</span><span class="selector-class">.stat</span>()</span><br><span class="line"></span><br><span class="line"> <span class="selector-tag">os</span><span class="selector-class">.chmod</span>() <span class="selector-tag">--</span>&gt; <span class="selector-tag">pathlib</span><span class="selector-class">.Path</span><span class="selector-class">.chmod</span>()</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>官网文档截图： <img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f2f489ba95c7?w=1268&amp;h=675&amp;f=png&amp;s=81475" alt=""> 详细请查看官方文档:<a href="https://docs.python.org/3/library/pathlib.html#methods" target="_blank" rel="noopener">插眼传送</a></p>
                  <h2 id="再举几个栗子"><a href="#再举几个栗子" class="headerlink" title="再举几个栗子"></a>再举几个栗子</h2>
                  <p>刚才的案例并不能说明什么，只是让我们了解到 pathlib 的构成，接下来让我们感受一下它带给我们的便捷。</p>
                  <h3 id="获取上层-上层目录"><a href="#获取上层-上层目录" class="headerlink" title="获取上层/上层目录"></a>获取上层/上层目录</h3>
                  <p>也就是获取它爷爷的名字 <img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f7ac5fcfc394?w=1197&amp;h=438&amp;f=png&amp;s=28570" alt=""> os 模块的写法为：</p>
                  <figure class="highlight lisp">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import os</span><br><span class="line"></span><br><span class="line">print(<span class="name">os</span>.path.dirname(<span class="name">os</span>.path.dirname(<span class="name">os</span>.getcwd())))</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f37dca2cee03?w=1702&amp;h=461&amp;f=png&amp;s=59217" alt=""> 如果用 pathlib 来实现：</p>
                  <figure class="highlight css">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="selector-tag">import</span> <span class="selector-tag">pathlib</span></span><br><span class="line"></span><br><span class="line"><span class="selector-tag">print</span>(<span class="selector-tag">pathlib</span><span class="selector-class">.Path</span><span class="selector-class">.cwd</span>()<span class="selector-class">.parent</span><span class="selector-class">.parent</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f3907795c105?w=1493&amp;h=383&amp;f=png&amp;s=57514" alt=""> parent 就完事了，这是不是更贴近 Pythonic ？ 像写英语一样写代码。 如果你只需要找到它爸爸，那就使用一次：</p>
                  <figure class="highlight css">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="selector-tag">import</span> <span class="selector-tag">pathlib</span></span><br><span class="line"></span><br><span class="line"><span class="selector-tag">print</span>(<span class="selector-tag">pathlib</span><span class="selector-class">.Path</span><span class="selector-class">.cwd</span>()<span class="selector-class">.parent</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>你还可以继续往祖辈上找：</p>
                  <figure class="highlight css">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="selector-tag">import</span> <span class="selector-tag">pathlib</span></span><br><span class="line"></span><br><span class="line"><span class="selector-tag">print</span>(<span class="selector-tag">pathlib</span><span class="selector-class">.Path</span><span class="selector-class">.cwd</span>()<span class="selector-class">.parent</span><span class="selector-class">.parent</span><span class="selector-class">.parent</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f3ce29deae54?w=1422&amp;h=363&amp;f=png&amp;s=53634" alt=""> 相对与之前 os 模块使用的多层 os.path.dirname，使用 parent 是不是便捷很多？</p>
                  <h3 id="路径拼接"><a href="#路径拼接" class="headerlink" title="路径拼接"></a>路径拼接</h3>
                  <p>如果你要在它爷爷辈那里拼接路径，那么你需要写这么长一串代码：</p>
                  <figure class="highlight lisp">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import os</span><br><span class="line"></span><br><span class="line">print(<span class="name">os</span>.path.join(<span class="name">os</span>.path.dirname(<span class="name">os</span>.path.dirname(<span class="name">os</span>.getcwd())), <span class="string">"关注"</span>, <span class="string">"微信公众号"</span>, <span class="string">"【进击的"</span>, <span class="string">"Coder】"</span>))</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f610d27e5947?w=1799&amp;h=398&amp;f=png&amp;s=56070" alt=""> 当你用 pathlib 的时候，你一定能够感受到快乐：</p>
                  <figure class="highlight stylus">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import pathlib</span><br><span class="line"></span><br><span class="line">parts = [<span class="string">"关注"</span>, <span class="string">"微信公众号"</span>, <span class="string">"【进击的"</span>, <span class="string">"Coder】"</span>]</span><br><span class="line"><span class="function"><span class="title">print</span><span class="params">(pathlib.Path.cwd()</span></span><span class="selector-class">.parent</span><span class="selector-class">.parent</span>.joinpath(*parts))</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>而且你还可以通过增加或减少 parent 的数量，来实现它祖辈的调节，美哉。</p>
                  <h1 id="PurePath"><a href="#PurePath" class="headerlink" title="PurePath"></a>PurePath</h1>
                  <p>上面的操作大部分都通过 pathlib 中的 Path 实现，其实它还有另一个模块 PurePath。</p>
                  <blockquote>
                    <p>PurePath 是一个纯路径对象，纯路径对象提供了实际上不访问文件系统的路径处理操作。有三种方法可以访问这些类，我们也称之为flavor。</p>
                  </blockquote>
                  <p>上面这句话来自于官方文档，听起来还是有点绕，我们还是通过栗子来了解它吧</p>
                  <h3 id="PurePath-match"><a href="#PurePath-match" class="headerlink" title="PurePath.match"></a>PurePath.match</h3>
                  <p>让我们来判断一下，当前文件路径是否有符合 ‘*.py’ 规则的文件</p>
                  <figure class="highlight lisp">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import pathlib</span><br><span class="line"></span><br><span class="line">print(<span class="name">pathlib</span>.PurePath(<span class="name">__file__</span>).match('*.py'))</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f9dfde47ae14?w=1476&amp;h=326&amp;f=png&amp;s=34765" alt=""> 很显然，我们编写代码的 coder.py 就符合规则，所以输出是 True。 为什么我要拿这个来举例呢？再深入想一下 pathlib.PurePath 后面能够跟着 match，那说明它应该是个对象，而不是一个路径字符串。为了验证这个想法，把代码改一改：</p>
                  <figure class="highlight stylus">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import pathlib</span><br><span class="line">import os</span><br><span class="line"></span><br><span class="line">os_path = os<span class="selector-class">.path</span>.dirname(__file__)</span><br><span class="line">pure_path = pathlib.PurePath(__file__)</span><br><span class="line"><span class="function"><span class="title">print</span><span class="params">(os_path, type(os_path)</span></span>)</span><br><span class="line"><span class="function"><span class="title">print</span><span class="params">(pure_path, type(pure_path)</span></span>)</span><br><span class="line"><span class="function"><span class="title">print</span><span class="params">(pathlib.PurePath(__file__)</span></span>.match(<span class="string">'*.py'</span>))</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/23/1673fa51dc777a36?w=1526&amp;h=540&amp;f=png&amp;s=74413" alt=""> 打印通过 os.path 获取当前路径的结果，得出一个路径字符串；而通过 pathlib.Pure 则获得的是一个 <strong>PurePosixPath</strong> 对象，并且得到的路径包括了当前文件 coder.py。 这就有点悬疑了， PurePosixPath 究竟是什么？ pathlib 可以操作两种文件系统的路径，一种是 Windows 文件系统，另一种称为非 Windows 文件系统，对应的对象是 <a href="PureWindowsPath">pathlib.PurePosixPath</a> 和 <a href="PureWindowsPath">PureWindowsPath</a>，不过不用担心，这些类并非是指定在某些操作系统上运行才能够使用，无论你运行的是哪个系统，都可以实例化所有这些类，因为它们不提供任何进行系统调用的操作。 不提供任何进行系统调用的操作，这又是什么？真是越听越深了 文档在最开始给出了这么一段描述:</p>
                  <blockquote>
                    <p>Pure paths are useful in some special cases; for example: If you want to manipulate Windows paths on a Unix machine (or vice versa). You cannot instantiate a WindowsPath when running on Unix, but you can instantiate PureWindowsPath. You want to make sure that your code only manipulates paths without actually accessing the OS. In this case, instantiating one of the pure classes may be useful since those simply don’t have any OS-accessing operations. 翻译：纯路径在某些特殊情况下很有用; 例如： 如果要在Unix计算机上操作Windows路径（反之亦然）。WindowsPath在Unix上运行时无法实例化，但可以实例化PureWindowsPath。 您希望确保您的代码仅操作路径而不实际访问操作系统。在这种情况下，实例化其中一个纯类可能很有用，因为那些只是没有任何操作系统访问操作。</p>
                  </blockquote>
                  <p>还附上了一张图： <img src="https://user-gold-cdn.xitu.io/2018/11/23/1673fae4d58d74a5?w=538&amp;h=495&amp;f=png&amp;s=18221" alt=""> 一下子也不是很理解，这是什么意思。不要紧，继续往下看。</p>
                  <h1 id="对应关系"><a href="#对应关系" class="headerlink" title="对应关系"></a>对应关系</h1>
                  <p>通过以上的例子我们可以感受到，它不仅封装了 os.path 相关常用方法，还集成了 os 的其他模块，比如创建文件夹 Path.mkdir。 如果你担心记不住，没关系的，文档一直都在。并且文档给我们列出了对应关系表 <img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f6944714cd2f?w=800&amp;h=637&amp;f=png&amp;s=56688" alt=""></p>
                  <h3 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h3>
                  <p>Path.iterdir()　　# 遍历目录的子目录或者文件 Path.is_dir()　　# 判断是否是目录 Path.glob()　　# 过滤目录(返回生成器) Path.resolve()　　# 返回绝对路径 Path.exists()　　# 判断路径是否存在 Path.open()　　# 打开文件(支持with) Path.unlink()　　# 删除文件或目录(目录非空触发异常)</p>
                  <h3 id="基本属性"><a href="#基本属性" class="headerlink" title="基本属性"></a>基本属性</h3>
                  <p>Path.parts　　# 分割路径 类似os.path.split(), 不过返回元组 Path.drive　　# 返回驱动器名称 Path.root　　# 返回路径的根目录 Path.anchor　　# 自动判断返回drive或root Path.parents　　# 返回所有上级目录的列表</p>
                  <h3 id="改变路径"><a href="#改变路径" class="headerlink" title="改变路径"></a>改变路径</h3>
                  <p>Path.with_name()　　# 更改路径名称, 更改最后一级路径名 Path.with_suffix()　　# 更改路径后缀</p>
                  <h3 id="拼接路径"><a href="#拼接路径" class="headerlink" title="拼接路径"></a>拼接路径</h3>
                  <p>Path.joinpath()　　# 拼接路径 Path.relative_to()　　# 计算相对路径</p>
                  <h3 id="测试路径"><a href="#测试路径" class="headerlink" title="测试路径"></a>测试路径</h3>
                  <p>Path.match()　　# 测试路径是否符合pattern Path.is_dir()　　# 是否是文件 Path.is_absolute()　　# 是否是绝对路径 Path.is_reserved()　　# 是否是预留路径 Path.exists()　　# 判断路径是否真实存在</p>
                  <h3 id="其他方法"><a href="#其他方法" class="headerlink" title="其他方法"></a>其他方法</h3>
                  <p>Path.cwd()　　# 返回当前目录的路径对象 Path.home()　　# 返回当前用户的home路径对象 Path.stat()　　# 返回路径信息, 同os.stat() Path.chmod()　　# 更改路径权限, 类似os.chmod() Path.expanduser()　　# 展开~返回完整路径对象 Path.mkdir()　　# 创建目录 Path.rename()　　# 重命名路径 Path.rglob()　　# 递归遍历所有子目录的文件</p>
                  <h1 id="pathlib-回顾"><a href="#pathlib-回顾" class="headerlink" title="pathlib 回顾"></a>pathlib 回顾</h1>
                  <p>通过上面的几个例子，我们对 pathlib 应该有一个大体的了解，接下来再回顾一下官方给 pathlib 库的定义：</p>
                  <blockquote>
                    <p>This module offers classes representing filesystem paths with semantics appropriate for different operating systems. Path classes are divided between pure paths, which provide purely computational operations without I/O, and concrete paths, which inherit from pure paths but also provide I/O operations. 释义：pathlib 提供表示文件系统路径的类，其语义适用于不同的操作系统。路径类在纯路径之间划分，纯路径提供纯粹的计算操作而没有I / O，以及具体路径，它继承纯路径但也提供I / O操作。</p>
                  </blockquote>
                  <p>回顾刚才这张图，重新理解 pathlib <img src="https://user-gold-cdn.xitu.io/2018/11/23/1673f6c43bcae0eb?w=538&amp;h=495&amp;f=png&amp;s=18074" alt=""> 如果你以前从未使用过这个模块，或者只是不确定哪个类适合您的任务，那么Path很可能就是您所需要的。它为代码运行的平台实例化一个具体路径。 <strong>总结：pathlib 不单纯是对 os 中一些模块或方法进行封装，而是为了兼容不同的操作系统，它为每类操作系统定义了接口。你希望在UNIX机器上操作Windows的路径，然而直接操作是做不到的，所以为你创建了一套接口 PurePath，你可以通过接口来实现你的目的（反之亦然）</strong></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/韦世东学算法和反爬虫" class="author" itemprop="url" rel="index">韦世东学算法和反爬虫</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-27 10:30:18" itemprop="dateCreated datePublished" datetime="2019-07-27T10:30:18+08:00">2019-07-27</time>
                </span>
                <span id="/6982.html" class="post-meta-item leancloud_visitors" data-flag-title="你还在用 os.path？快来感受一下 pathlib 给你带来的便捷吧！" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>4.7k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>4 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6970.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> 技术杂谈 <i class="label-arrow"></i>
                  </a>
                  <a href="/6970.html" class="post-title-link" itemprop="url">5G时代谁领风骚：先看看2G、3G、4G时代都崛起了哪些公司？</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>6月6日工信部正式向中国电信、中国移动、中国联通、中国广电发放5G商用牌照。自此，中国正式进入5G商用元年。 就中国而讲，2G时代门户网站林立、社交软件初起，3G时代智能手机大战、社交类软件成为王者，4G时代直播与短视频风光无二、信息流成热话题，那5G时代谁能独领风骚？ 在5G还未全面普及之前，我们回顾一下并不遥远的昨天，在2G、3G、4G的兴起时，哪些公司应运而生，成为巨头，哪些巨头又轰然倒塌，成为历史的一部分。 <strong>2G时代，QQ崛起，阿里京东起家，门户网站林立</strong> 中国互联网的发展至今也只20年有余，自20世纪90年代末，中国人从1G时代代表性手机大哥大（摩托罗拉）手机换成了2G时代代表性手机诺基亚，彼时手机不只是接打电话，增加了发短信甚至邮件的功能。 20世纪90年代末期中国，还处在纸媒、广播和电视一统天下的传统媒体时代，但经济的迅猛发展已经造成信息饥渴，彼时媒体所提供的信息无论从丰富程度还是传播速度上都很难满足用户需求。 一个从美国麻省理工学院（MIT）攻读博士学位归来的“天之骄子”带回了一个新鲜事物，由于被美国互联网的发展深深震撼，张朝阳回国创建了中国第一家门户网站——搜狐。那是在1998年，阿里巴巴还没诞生，马化腾的QQ也未问世，李彦宏还在美国硅谷，中国互联网的舞台上，也只有张朝阳和他的搜狐。 竞争对手还未成长，搜狐独享巨大的互联网红利，2000年就上市，占据着中国门户网站头部企业的位置。 同样1998年，在搜狐成立后不到10个月，王志东的新浪网于年底成立，当时的口号是在互联网上建立全球最大的华人网站。以体育新闻传播为起点的新浪迅速扩张，进阶成为综合性新闻门户。 当时中国门户网站受资本青睐的程度，以及上市的速度堪称一个时代的奇迹，新浪网虽成立晚了搜狐10个月，但却比搜狐早了3个月在纳斯达克敲钟。 与搜狐新浪以门户网站起家不同，网易1997年成立之初以搜索引擎和免费邮箱系统立身，赶上门户网站热潮席卷中国，1998年丁磊带领网易挥师北上，落户北京，公司战略也从“系统集成商”正式转向“互联网服务提供商”。自此，网易与搜狐、新浪成为中国三大门户网站。 在张朝阳、丁磊等都借互联网东风大搞门户网站捞第一波红利的时候，在深圳创业的马化腾开始琢磨做社交。马化腾与大学同学合伙成立腾讯，当时主要做“无线网络寻呼系统”，就是将互联网和寻呼机结合，使得寻呼机可以收到互联网的传唤，并且可以收看新闻乃至电子邮件等功能。 但不可逆的是当时寻呼机业务已处于颓势，1998年，身处2G时代，手机在市场上肆意生长。马化腾当机立断，转头做IM（即时通讯软件），OICQ应运而生，OICQ1999年2月10日发布了第一个版本——OICQ 99 beta build 0210，就此，颠覆未来20年中文互联网的明星产品QQ诞生了。 此后腾讯围绕QQ延展出多款产品，包括QQ宠物、QQ空间、QQ游戏等，这为腾讯带来盈利的同时留存了海量互联网用户。QQ在中国整个2G时代都占据着社交软件第一把交椅，为腾讯后续布局游戏等可以提供天然入口，这让腾讯的产品可以轻而易举地碾压竞品。 而之后改变中国互联网格局的阿里巴巴也是在2G时代诞生的。1999年，马云带领下的18为创始人创建阿里巴巴集团。2003年，淘宝网创立，作为新生事物的淘宝网出奇制胜——没和ebay易趣争抢既有的存量市场，而是收割疯狂生长的增量市场；仅仅通过1年时间，这家“倒过来看世界”的互联网公司，就成了中国网络购物市场的领军企业。 而2004年，支付宝创立，中国第三方支付模式雏形初现。值得一提的是，同年诞生的淘宝旺旺将即时聊天工具和网络购物相联系起来，是阿里巴巴做网上零售的法宝。这时候的淘宝旺旺并没有和QQ对标，还处在阿里腾讯之间还是各自领域狂奔的时代。 在2005年淘宝的成交额就突破80亿元，甚至超越了沃尔玛。 在马云创立淘宝网，大搞电商之时，在中关村卖了几年光盘的刘强东坐不住了，2004年，京东多媒体网正式开通。或许与刘强东卖3C产品起家有关，京东电子商务平台是主打3C产品，与淘宝对标的同时又有自己清晰的定位，虽然都是做电商，但同质化并不严重。 <strong>3G时代，智能手机展露头角，巨头大战社交</strong> 整个2G时代其实属于PC端互联网，2G的手机只能打电话发短信，上网很困难。但3G通信标准将信息传输率提高了一个数量级，这是一个飞跃，3G时代真正意义上而言是移动互联网的开端，从此手机打电话的功能降到了次要的位置，而数据通信，也就是上网，成为了主要功能。 乔布斯2007年拿着一款智能手机iPhone1出现，宣布苹果主宰移动互联网时代的开始。从此手机不再以功能为主，而是以应用软件（APP）为主，APP store 更是一个划时代产品，让用户可以轻而易举地购买下载所需的应用。 中国的3G时代稍晚于美国，2009年1月，工信部为中国移动、联通、电信发布3G牌照，中国从此进入3G时代。 在智能手机初探中国市场的时间节点，三星、诺基亚、HTC等占据了巨大部分江山，而中国2G时代的功能机科健、波导、海尔等相继淡出市场，国产的智能机中兴、华为、酷派、联想等完全靠三大运营商的销售渠道生存，打着性价比旗号的合约机始终比不上价格更高的三星、HTC的中低端手机。 但2011年小米的横空出世打破了这个壁垒。3G网络在中国越来越普及，彼时在国外品牌占据中国市场，而国产智能手机厂商仅能依附三大运营商销售，而当苹果手机最具冲击力的一代iPhone4席卷全球时，一个叫雷军的人拿出了他的第一代产品——小米1，冲进中国智能手机市场。 三星与HTC主流机型定价4000元，而小米1999的超高性价比＋饥饿营销，搅动了中国智能手机市场，成为3G时代崛起的手机巨头，2012年2月，“屌丝”一词横空出世，有人说它是为屌丝而生，谁能想到日后它更是成为世界最年轻的500强？ 如果说QQ的问世是一个偶然，那微信出道是无数竞争的结果。 移动互联网兴起，当然PC端依然沉淀着大量用户，彼时的QQ为了顾及PC端的用户，界面、功能更照顾PC端，以至于没能及时赶上移动互联网的浪潮，但一个叫张小龙的人彻底颠覆了社交。 2011年1月，微信横空出世，这款主打IM的应用程序契合3G时代的特点，可以发送文字、语音、图片、视频等，一出现就借助QQ的天然优势，并打通通信录，迅速推广，以至于后续发展成为国民软件，腾讯也借此拿到了通向移动互联网第一张门票，腾讯能成为今天的巨头公司，而且地位难以撼动，微信功不可没。 其实在腾讯布局社交之时，2G时代崛起的门户大咖搜狐、新浪也瞄准了社交这个大蛋糕。只不过新浪瞄准的是信息即时分享，而搜狐一直在拾人牙慧。新浪微博自2009年一经问世便牢牢掌控着微博头把交椅的位置，就连腾讯微博的冲击也未动摇半分。 搜狐看QQ火爆便做了搜Q，见微博火爆便做了搜狐微博，还做了社区社交“白社会”，但无一例外遭遇失败。连张朝阳本人都说，“微信微博左右扇了我两个耳光。”最近张朝阳又推出“狐友”，硬要推着石头上山的张朝阳不知还有没有气力。 而前面提到2G时代阿里推出淘宝旺旺却忙着做电商，没有正面硬刚QQ，但此时看到微信在庞大的社交领域带来的巨大流量时，马云坐不住了。2013年，阿里巴巴推出了来往，这是阿里推出的即时通讯软件，也是阿里第一款独立于电商业务之外的社交产品，其核心功能是实现熟人之间的社交。 原因是马云认为腾讯已经“侵入”了阿里的地盘，要用来往去砸微信的场子，但结果大家都知晓了。但马云并不灰心，之后用支付宝做社交还是失败，只是钉钉的成功才稍找回点面子。 另外值得一提的是，3G时代，LBS应用于地图等会对4G时代滴滴、共享单车等的崛起起到推手的作用。 <strong>4G时代，团购直播视频手游异军突起，共享出行风口正盛，巨头布局信息流</strong> 如果说1G到2G是划时代的进步，而3G的短暂存在只是一个过渡，因为短短几年后，网速产生质的飞跃的4G时代迅速到来。 3G传播速度相对2G较快，可以较好满足手机上网等需求，只是播放高清视频还是比较困难。而4G的速度几乎满足无线用户所有需求。 彼时移动互联网光速发展，大面积吞噬PC互联网流量和用户。比PC端更加便捷的移动端生活服务类应用风靡中国，O2O模式成了风口上的猪。巨头们纷纷布局，阿里有口碑网，腾讯有微团购，百度则买来糯米网，但谁也不曾想最后的赢家不是巨头而是王兴的美团网，2015年，美团网与大众点评合并，重构了O2O模式。 美团成为4G时代崛起的巨头，而腾讯依靠着微信的社交流量巨大入口也不会败，阿里则有支付宝这个生活服务类的大平台，甚至日后提出新零售概念取代O2O成为新的风口，这场大战里，仿佛只有百度掉队了，从PC互联网时代跨到移动互联网时代，百度要做的产品是在抢夺自己PC端的用户，这让百度似乎有点手足无措。 网速加快受益最大的无疑是直播与视频。2016年前后，资本涌入直播赛道，以斗鱼、虎牙、YY、熊猫、全民等为首的游戏直播平台，纷纷宣布获得融资，疯狂烧钱抢夺主播和用户，以映客、花椒等移动端为主的直播也趁势而起。 彼时直播行业一片混乱，违法违规直播大行其道，随着监管力度加大，资本退场，直播赛道也一地鸡毛，也只是留下虎牙、斗鱼等直播巨头。 4G时代网速加快的同时，运营商提速降费，这让依靠文字和图片获取信息的用户越来越不满足，短视频有着天时地利人和，在用户需求之下诞生。其中以抖音、快手为代表的的社交媒体类短视频最为火爆，而以秒拍、西瓜为代表的的新闻资讯类也广受媒体和用户欢迎，以B站为首的BBS类更是抓住了细分市场。 今年年初Vlog的流行也只是吹了一阵，现已没有当时的热度，看起来短视频的风还会继续吹下去。 2G时代的三大门户巨头搜狐新浪和网易，如今搜狐和新浪的体量与网易已不是一个级别的，新浪尚有微博撑着场面，搜狐却实实在在地掉队了，网易又是如何始终保持盈利的呢？其中一个很重要的支撑点就是网易的游戏帝国，而4G时代更让网易如虎添翼。 曾经风靡全国的PC端回合制游戏“梦幻西游”，手机版一经上线便俘获众多老玩家，这个游戏特点之一就是烧钱。一位玩梦幻的朋友告诉盒饭财经，这个游戏里，充值几万也只是低端玩家而已，动辄砸百万的大有人在，而广受女性玩家喜爱的手游阴阳师也是一个人民币玩家的游戏。 从网易公布的财报不难看出，近几年网易游戏收入占每季度总收入都超过6成，与还靠着新闻资讯的新浪搜狐不同，网易应4G时代实现业务重心迁徙，还能算是互联网一线巨头。 守着微信这个巨大流量入口的腾讯布局游戏更显得理所当然，从PC端的QQ衍生出的众多小游戏为起点，到英雄联盟的大火，3G时代的天天酷跑，再到4G移动互联网时代手游吃鸡（和平精英）、王者荣耀，腾讯在每个时代都在游戏产业上走得很稳。 目前，腾讯游戏占中国游戏市场规模的5成以上，不过与网易不同的是，腾讯一直追求的是薄利多销策略。游戏内道具价格相比网易较为低廉，能取得超过千亿的年收入，全靠用户基数众多。 在PC端流量急剧下降，移动端用户暴涨之时，广告投放方也开始思考有的放矢，PC端时代是买广告位，而4G大数据时代就是买用户，这也称为信息流广告。 信息流广告最大的优势是不浪费资源，运用大数据针对性投放，既然有利可图，便成为各大巨头争先布局的板块，而这其中最为突出的就是今日头条和百度。 说起今日头条，这个曾经“小而美”的公司今年内发展成为TMD（头条、美团、滴滴）的首字母巨头，核心是张一鸣，但也得益于4G时代的浪潮。 今日头条的大获成功最引以为傲的是算法，虽然是新闻资讯类产品，但张一鸣的主创团队全是技术，不需要文字编辑。他们只做一件事，用纯技术算法手段从海量的内容中去搜索挖掘有价值的内容，最关键的是这些内容可以根据客户的需要进行“定制化”推送。这在信息爆炸时代，人们可以摆脱浩瀚无垠的信息海洋，只读取精准定制的有价值信息。 而依靠着强大的算法，什么火他做什么。微博火，做微头条，知乎火，做悟空问答，短视频直播时代到来，做火山小视频、西瓜视频、抖音，头条系产品依靠其强大的算法打造着一个又一个爆款。 2G时代，两大电商平台阿里和京东相争，虽然不是一个体量级，但是淘宝假货风行，一度让阿里痛下决心清理商家，而这些商家顺势被一个叫黄峥的人收走了，2015年，主打社交电商的拼多多应运而生。对了，这个黄峥还跟着别人和巴菲特吃过午餐。 而拼多多背后除了创始人黄峥，腾讯是第二大股东，而微信的天然社交入口让拼多多尝够了甜头，短短三年便上市，如今月活量已超过京东直逼淘宝。很显然，腾讯非常乐见有一家公司能威胁到阿里的主业务。 前文提到，LBS的应用为4G时代的滴滴发展起到推手的作用，2012年滴滴成立以来直至2014年都是缓慢上升期。但是自2014年以后，4G手机进一步普及，滴滴迅猛发展，短短一年便成为出行当之无愧的No.1，而滴滴2014年接入微信，这为滴滴提供了天然的流量入口。 但滴滴的竞品快的接入支付宝，此时阿里与腾讯在各个重合的领域相争已是司空见惯。但当时占领市场只有一个秘诀——烧钱，各种补贴各种红包雨，滴滴快的的烧钱战略持续了相当长一段时间，直到2015年滴滴快的的合并，让出行市场成为一家独大。 如果说滴滴是划时代的出行产品，那共享单车的出现引领了一个共享经济时代。 2004年，一个年轻人连同4个校友，提出“以共享经济+智能硬件，解决最后一公里出行问题”的经营理念，创立ofo共享单车项目，起先他们通过定制，将自行车通过车身号、机械锁绑定APP的方式，提供密码解锁用车的方式，在北京大学推出这一项目。这个项目的创始人叫戴威，北京大学光华管理学院的毕业生。 2015年1月27日，做媒体出身的胡玮炜和运营大牛王晓峰，在北京成立了一家名为“北京摩拜科技有限公司“的公司，他们的愿景是“让自行车回归生活”。 自此，共享单车风口吹起，共享单车“颜色大战”一触即发。而中国互联网创投倒贴钱抢市场，等日后一家独大再赚钱的逻辑，再一次在共享单车上体现的淋漓尽致。当时也就ofo和摩拜两家独大，但风口过得似乎有些快，风口过后，一地鸡毛。摩拜卖身美团，ofo深陷押金风波半死不活，老三哈罗单车如今倒是坐收渔翁之利。 <strong>5G时代，谁又能领风骚？</strong> 2G、3G、4G时代，随着互联网的发展，一些巨头应时代而生，而一些公司没能及时跟上时代的发展而掉队，还有像腾讯阿里等长盛不衰而又相互掣肘。2G、3G已然开始退网，4G时代方兴未艾，而5G时代紧赶着来了。 5G会直接加速万物互联的进程，改变我们与世界的交互界面。除了VR游戏、无人驾驶、智能物联等应用外，移动办公、会议直播、视频监控、智能城市等都会在5G的大网络下运行。智能手机厂商忙着推出5G手机，而互联网科技巨头在推出5G应用，虽然用户还未感觉到5G的速度，但这个赛道已经热得发烫。 阿里腾讯继续两强争霸？还是迷途的百度能否东山再起？是否还有头条、美团一样突然崛起的巨头？5G时代，一切皆有可能。 来源：<a href="https://www.chinaventure.com.cn/news/83-20190726-346438.html" target="_blank" rel="noopener">https://www.chinaventure.com.cn/news/83-20190726-346438.html</a></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-26 11:37:43" itemprop="dateCreated datePublished" datetime="2019-07-26T11:37:43+08:00">2019-07-26</time>
                </span>
                <span id="/6970.html" class="post-meta-item leancloud_visitors" data-flag-title="5G时代谁领风骚：先看看2G、3G、4G时代都崛起了哪些公司？" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>6.1k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>6 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6942.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> 技术杂谈 <i class="label-arrow"></i>
                  </a>
                  <a href="/6942.html" class="post-title-link" itemprop="url">Python 爬虫利器之 Pyppeteer 的用法</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>如果大家对 Python 爬虫有所了解的话，想必你应该听说过 Selenium 这个库，这实际上是一个自动化测试工具，现在已经被广泛用于网络爬虫中来应对 JavaScript 渲染的页面的抓取。 但 Selenium 用的时候有个麻烦事，就是环境的相关配置，得安装好相关浏览器，比如 Chrome、Firefox 等等，然后还要到官方网站去下载对应的驱动，最重要的还需要安装对应的 Python Selenium 库，确实是不是很方便，另外如果要做大规模部署的话，环境配置的一些问题也是个头疼的事情。 那么本节就介绍另一个类似的替代品，叫做 Pyppeteer。注意，是叫做 Pyppeteer，不是 Puppeteer。Puppeteer 是 Google 基于 Node.js 开发的一个工具，有了它我们可以通过 JavaScript 来控制 Chrome 浏览器的一些操作，当然也可以用作网络爬虫上，其 API 极其完善，功能非常强大。 而 Pyppeteer 又是什么呢？它实际上是 Puppeteer 的 Python 版本的实现，但他不是 Google 开发的，是一位来自于日本的工程师依据 Puppeteer 的一些功能开发出来的非官方版本。 在 Pyppetter 中，实际上它背后也是有一个类似 Chrome 浏览器的 Chromium 浏览器在执行一些动作进行网页渲染，首先说下 Chrome 浏览器和 Chromium 浏览器的渊源。</p>
                  <blockquote>
                    <p>Chromium 是谷歌为了研发 Chrome 而启动的项目，是完全开源的。二者基于相同的源代码构建，Chrome 所有的新功能都会先在 Chromium 上实现，待验证稳定后才会移植，因此 Chromium 的版本更新频率更高，也会包含很多新的功能，但作为一款独立的浏览器，Chromium 的用户群体要小众得多。两款浏览器“同根同源”，它们有着同样的 Logo，但配色不同，Chrome 由蓝红绿黄四种颜色组成，而 Chromium 由不同深度的蓝色构成。</p>
                  </blockquote>
                  <p><img src="https://qiniu.cuiqingcai.com/2019-07-26-image-20190325031516968.png" alt=""> 总的来说，两款浏览器的内核是一样的，实现方式也是一样的，可以认为是开发版和正式版的区别，功能上基本是没有太大区别的。 Pyppeteer 就是依赖于 Chromium 这个浏览器来运行的。那么有了 Pyppeteer 之后，我们就可以免去那些繁琐的环境配置等问题。如果第一次运行的时候，Chromium 浏览器没有安全，那么程序会帮我们自动安装和配置，就免去了繁琐的环境配置等工作。另外 Pyppeteer 是基于 Python 的新特性 async 实现的，所以它的一些执行也支持异步操作，效率相对于 Selenium 来说也提高了。 那么下面就让我们来一起了解下 Pyppeteer 的相关用法吧。</p>
                  <h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2>
                  <p>首先就是安装问题了，由于 Pyppeteer 采用了 Python 的 async 机制，所以其运行要求的 Python 版本为 3.5 及以上。 安装方式非常简单：</p>
                  <figure class="highlight cmake">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">pip3 <span class="keyword">install</span> pyppeteer</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>好了，安装完成之后我们命令行下测试下：</p>
                  <figure class="highlight elm">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">\&gt;&gt;&gt; <span class="keyword">import</span> pyppeteer</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>如果没有报错，那么就证明安装成功了。</p>
                  <h2 id="快速上手"><a href="#快速上手" class="headerlink" title="快速上手"></a>快速上手</h2>
                  <p>接下来我们测试下基本的页面渲染操作，这里我们选用的网址为：<a href="http://quotes.toscrape.com/js/，这个页面是" target="_blank" rel="noopener">http://quotes.toscrape.com/js/，这个页面是</a> JavaScript 渲染而成的，用基本的 requests 库请求得到的 HTML 结果里面是不包含页面中所见的条目内容的。 为了证明 requests 无法完成正常的抓取，我们可以先用如下代码来测试一下：</p>
                  <figure class="highlight perl">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import requests</span><br><span class="line">from pyquery import PyQuery as pq</span><br><span class="line"></span><br><span class="line">url = <span class="string">'http://quotes.toscrape.com/js/'</span></span><br><span class="line">response = requests.get(url)</span><br><span class="line">doc = p<span class="string">q(response.text)</span></span><br><span class="line"><span class="keyword">print</span>(<span class="string">'Quotes:'</span>, doc(<span class="string">'.quote'</span>).length)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这里首先使用 requests 来请求网页内容，然后使用 pyquery 来解析页面中的每一个条目。观察源码之后我们发现每个条目的 class 名为 quote，所以这里选用了 .quote 这个 CSS 选择器来选择，最后输出条目数量。 运行结果：</p>
                  <figure class="highlight yaml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="attr">Quotes:</span> <span class="number">0</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>结果是 0，这就证明使用 requests 是无法正常抓取到相关数据的。因为什么？因为这个页面是 JavaScript 渲染而成的，我们所看到的内容都是网页加载后又执行了 JavaScript 之后才呈现出来的，因此这些条目数据并不存在于原始 HTML 代码中，而 requests 仅仅抓取的是原始 HTML 代码。 好的，所以遇到这种类型的网站我们应该怎么办呢？ 其实答案有很多：</p>
                  <ul>
                    <li>分析网页源代码数据，如果数据是隐藏在 HTML 中的其他地方，以 JavaScript 变量的形式存在，直接提取就好了。</li>
                    <li>分析 Ajax，很多数据可能是经过 Ajax 请求时候获取的，所以可以分析其接口。</li>
                    <li>模拟 JavaScript 渲染过程，直接抓取渲染后的结果。</li>
                  </ul>
                  <p>而 Pyppeteer 和 Selenium 就是用的第三种方法，下面我们再用 Pyppeteer 来试试，如果用 Pyppeteer 实现如上页面的抓取的话，代码就可以写为如下形式：</p>
                  <figure class="highlight python">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> pyppeteer <span class="keyword">import</span> launch</span><br><span class="line"><span class="keyword">from</span> pyquery <span class="keyword">import</span> PyQuery <span class="keyword">as</span> pq</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line">    browser = <span class="keyword">await</span> launch()</span><br><span class="line">    page = <span class="keyword">await</span> browser.newPage()</span><br><span class="line">    <span class="keyword">await</span> page.goto(<span class="string">'http://quotes.toscrape.com/js/'</span>)</span><br><span class="line">    doc = pq(<span class="keyword">await</span> page.content())</span><br><span class="line">    print(<span class="string">'Quotes:'</span>, doc(<span class="string">'.quote'</span>).length)</span><br><span class="line">    <span class="keyword">await</span> browser.close()</span><br><span class="line"></span><br><span class="line">asyncio.get_event_loop().run_until_complete(main())</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>运行结果：</p>
                  <figure class="highlight yaml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="attr">Quotes:</span> <span class="number">10</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>看运行结果，这说明我们就成功匹配出来了 class 为 quote 的条目，总数为 10 条，具体的内容可以进一步使用 pyquery 解析查看。 那么这里面的过程发生了什么？ 实际上，Pyppeteer 整个流程就完成了浏览器的开启、新建页面、页面加载等操作。另外 Pyppeteer 里面进行了异步操作，所以需要配合 async/await 关键词来实现。 首先， launch 方法会新建一个 Browser 对象，然后赋值给 browser，然后调用 newPage 方法相当于浏览器中新建了一个选项卡，同时新建了一个 Page 对象。然后 Page 对象调用了 goto 方法就相当于在浏览器中输入了这个 URL，浏览器跳转到了对应的页面进行加载，加载完成之后再调用 content 方法，返回当前浏览器页面的源代码。然后进一步地，我们用 pyquery 进行同样地解析，就可以得到 JavaScript 渲染的结果了。 另外其他的一些方法如调用 asyncio 的 get_event_loop 等方法的相关操作则属于 Python 异步 async 相关的内容了，大家如果不熟悉可以了解下 Python 的 async/await 的相关知识。 好，通过上面的代码，我们就可以完成 JavaScript 渲染页面的爬取了。 在这个过程中，我们没有配置 Chrome 浏览器，没有配置浏览器驱动，免去了一些繁琐的步骤，同样达到了 Selenium 的效果，还实现了异步抓取，爽歪歪！ 接下来我们再看看另外一个例子，这个例子可以模拟网页截图，保存 PDF，另外还可以执行自定义的 JavaScript 获得特定的内容，代码如下：</p>
                  <figure class="highlight python">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> pyppeteer <span class="keyword">import</span> launch</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line">    browser = <span class="keyword">await</span> launch()</span><br><span class="line">    page = <span class="keyword">await</span> browser.newPage()</span><br><span class="line">    <span class="keyword">await</span> page.goto(<span class="string">'http://quotes.toscrape.com/js/'</span>)</span><br><span class="line">    <span class="keyword">await</span> page.screenshot(path=<span class="string">'example.png'</span>)</span><br><span class="line">    <span class="keyword">await</span> page.pdf(path=<span class="string">'example.pdf'</span>)</span><br><span class="line">    dimensions = <span class="keyword">await</span> page.evaluate(<span class="string">'''() =&gt; &#123;</span></span><br><span class="line"><span class="string">        return &#123;</span></span><br><span class="line"><span class="string">            width: document.documentElement.clientWidth,</span></span><br><span class="line"><span class="string">            height: document.documentElement.clientHeight,</span></span><br><span class="line"><span class="string">            deviceScaleFactor: window.devicePixelRatio,</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string">    &#125;'''</span>)</span><br><span class="line"></span><br><span class="line">    print(dimensions)</span><br><span class="line">    <span class="comment"># &gt;&gt;&gt; &#123;'width': 800, 'height': 600, 'deviceScaleFactor': 1&#125;</span></span><br><span class="line">    <span class="keyword">await</span> browser.close()</span><br><span class="line"></span><br><span class="line">asyncio.get_event_loop().run_until_complete(main())</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这里我们又用到了几个新的 API，完成了网页截图保存、网页导出 PDF 保存、执行 JavaScript 并返回对应数据。 首先 screenshot 方法可以传入保存的图片路径，另外还可以指定保存格式 type、清晰度 quality、是否全屏 fullPage、裁切 clip 等各个参数实现截图。 截图的样例如下： <img src="https://qiniu.cuiqingcai.com/2019-07-26-image-20190325095645072.png" alt=""> 可以看到它返回的就是 JavaScript 渲染后的页面。 pdf 方法也是类似的，只不过页面保存格式不一样，最后得到一个多页的 pdf 文件，样例如下： <img src="https://qiniu.cuiqingcai.com/2019-07-26-image-20190325095759738.png" alt=""> 可见其内容也是 JavaScript 渲染后的内容，另外这个方法还可以指定放缩大小 scale、页码范围 pageRanges、宽高 width 和 height、方向 landscape 等等参数，导出定制化的 pdf 用这个方法就十分方便。 最后我们又调用了 evaluate 方法执行了一些 JavaScript，JavaScript 传入的是一个函数，使用 return 方法返回了网页的宽高、像素大小比率三个值，最后得到的是一个 JSON 格式的对象，内容如下：</p>
                  <figure class="highlight yaml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="string">&#123;'width':</span> <span class="number">800</span><span class="string">,</span> <span class="attr">'height':</span> <span class="number">600</span><span class="string">,</span> <span class="attr">'deviceScaleFactor':</span> <span class="number">1</span><span class="string">&#125;</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>OK，实例就先感受到这里，还有太多太多的功能还没提及。 总之利用 Pyppeteer 我们可以控制浏览器执行几乎所有动作，想要的操作和功能基本都可以实现，用它来自由地控制爬虫当然就不在话下了。</p>
                  <h2 id="详细用法"><a href="#详细用法" class="headerlink" title="详细用法"></a>详细用法</h2>
                  <p>了解了基本的实例之后，我们再来梳理一下 Pyppeteer 的一些基本和常用操作。Pyppeteer 的几乎所有功能都能在其官方文档的 API Reference 里面找到，链接为：<a href="https://miyakogi.github.io/pyppeteer/reference.html，用到哪个方法就来这里查询就好了，参数不必死记硬背，即用即查就好" target="_blank" rel="noopener">https://miyakogi.github.io/pyppeteer/reference.html，用到哪个方法就来这里查询就好了，参数不必死记硬背，即用即查就好</a>。</p>
                  <h3 id="开启浏览器"><a href="#开启浏览器" class="headerlink" title="开启浏览器"></a>开启浏览器</h3>
                  <p>使用 Pyppeteer 的第一步便是启动浏览器，首先我们看下怎样启动一个浏览器，其实就相当于我们点击桌面上的浏览器图标一样，把它开起来。用 Pyppeteer 完成同样的操作，只需要调用 launch 方法即可。 我们先看下 launch 方法的 API，链接为：<a href="https://miyakogi.github.io/pyppeteer/reference.html#pyppeteer.launcher.launch" target="_blank" rel="noopener">https://miyakogi.github.io/pyppeteer/reference.html#pyppeteer.launcher.launch</a>，其方法定义如下：</p>
                  <figure class="highlight less">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="selector-tag">pyppeteer</span><span class="selector-class">.launcher</span><span class="selector-class">.launch</span>(<span class="attribute">options</span>: dict = None, **kwargs) → <span class="selector-tag">pyppeteer</span><span class="selector-class">.browser</span><span class="selector-class">.Browser</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>可以看到它处于 launcher 模块中，参数没有在声明中特别指定，返回类型是 browser 模块中的 Browser 对象，另外观察源码发现这是一个 async 修饰的方法，所以调用它的时候需要使用 await。 接下来看看它的参数：</p>
                  <ul>
                    <li>ignoreHTTPSErrors (bool): 是否要忽略 HTTPS 的错误，默认是 False。</li>
                    <li>headless (bool): 是否启用 Headless 模式，即无界面模式，如果 devtools 这个参数是 True 的话，那么该参数就会被设置为 False，否则为 True，即默认是开启无界面模式的。</li>
                    <li>executablePath (str): 可执行文件的路径，如果指定之后就不需要使用默认的 Chromium 了，可以指定为已有的 Chrome 或 Chromium。</li>
                    <li>slowMo (int|float): 通过传入指定的时间，可以减缓 Pyppeteer 的一些模拟操作。</li>
                    <li>args (List[str]): 在执行过程中可以传入的额外参数。</li>
                    <li>ignoreDefaultArgs (bool): 不使用 Pyppeteer 的默认参数，如果使用了这个参数，那么最好通过 args 参数来设定一些参数，否则可能会出现一些意想不到的问题。这个参数相对比较危险，慎用。</li>
                    <li>handleSIGINT (bool): 是否响应 SIGINT 信号，也就是可以使用 Ctrl + C 来终止浏览器程序，默认是 True。</li>
                    <li>handleSIGTERM (bool): 是否响应 SIGTERM 信号，一般是 kill 命令，默认是 True。</li>
                    <li>handleSIGHUP (bool): 是否响应 SIGHUP 信号，即挂起信号，比如终端退出操作，默认是 True。</li>
                    <li>dumpio (bool): 是否将 Pyppeteer 的输出内容传给 process.stdout 和 process.stderr 对象，默认是 False。</li>
                    <li>userDataDir (str): 即用户数据文件夹，即可以保留一些个性化配置和操作记录。</li>
                    <li>env (dict): 环境变量，可以通过字典形式传入。</li>
                    <li>devtools (bool): 是否为每一个页面自动开启调试工具，默认是 False。如果这个参数设置为 True，那么 headless 参数就会无效，会被强制设置为 False。</li>
                    <li>logLevel (int|str): 日志级别，默认和 root logger 对象的级别相同。</li>
                    <li>autoClose (bool): 当一些命令执行完之后，是否自动关闭浏览器，默认是 True。</li>
                    <li>loop (asyncio.AbstractEventLoop): 时间循环对象。</li>
                  </ul>
                  <p>好了，知道这些参数之后，我们可以先试试看。 首先可以试用下最常用的参数 headless，如果我们将它设置为 True 或者默认不设置它，在启动的时候我们是看不到任何界面的，如果把它设置为 False，那么在启动的时候就可以看到界面了，一般我们在调试的时候会把它设置为 False，在生产环境上就可以设置为 True，我们先尝试一下关闭 headless 模式：</p>
                  <figure class="highlight python">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> pyppeteer <span class="keyword">import</span> launch</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">await</span> launch(headless=<span class="literal">False</span>)</span><br><span class="line">    <span class="keyword">await</span> asyncio.sleep(<span class="number">100</span>)</span><br><span class="line"></span><br><span class="line">asyncio.get_event_loop().run_until_complete(main())</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>运行之后看不到任何控制台输出，但是这时候就会出现一个空白的 Chromium 界面了： <img src="https://qiniu.cuiqingcai.com/2019-07-26-image-20190416075926432.png" alt=""> 但是可以看到这就是一个光秃秃的浏览器而已，看一下相关信息： <img src="https://qiniu.cuiqingcai.com/2019-07-26-image-20190416080103093.png" alt=""> 看到了，这就是 Chromium，上面还写了开发者内部版本，可以认为是开发版的 Chrome 浏览器就好。 另外我们还可以开启调试模式，比如在写爬虫的时候会经常需要分析网页结构还有网络请求，所以开启调试工具还是很有必要的，我们可以将 devtools 参数设置为 True，这样每开启一个界面就会弹出一个调试窗口，非常方便，示例如下：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import asyncio</span><br><span class="line"><span class="keyword">from</span> pyppeteer import launch</span><br><span class="line"></span><br><span class="line">async def main():</span><br><span class="line">    browser = await launch(<span class="attribute">devtools</span>=<span class="literal">True</span>)</span><br><span class="line">   <span class="built_in"> page </span>= await browser.newPage()</span><br><span class="line">    await page.goto(<span class="string">'https://www.baidu.com'</span>)</span><br><span class="line">    await asyncio.sleep(100)</span><br><span class="line"></span><br><span class="line">asyncio.get_event_loop().run_until_complete(main())</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>刚才说过 devtools 这个参数如果设置为了 True，那么 headless 就会被关闭了，界面始终会显现出来。在这里我们新建了一个页面，打开了百度，界面运行效果如下： <img src="https://qiniu.cuiqingcai.com/2019-07-26-image-20190416081010098.png" alt=""> 这时候我们可以看到上面的一条提示：”Chrome 正受到自动测试软件的控制”，这个提示条有点烦，那咋关闭呢？这时候就需要用到 args 参数了，禁用操作如下：</p>
                  <figure class="highlight ini">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="attr">browser</span> = await launch(headless=<span class="literal">False</span>, args=[<span class="string">'--disable-infobars'</span>])</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这里就不再写完整代码了，就是在 launch 方法中，args 参数通过 list 形式传入即可，这里使用的是 —disable-infobars 的参数。 另外有人就说了，这里你只是把提示关闭了，有些网站还是会检测到是 webdriver 吧，比如淘宝检测到是 webdriver 就会禁止登录了，我们可以试试：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import asyncio</span><br><span class="line"><span class="keyword">from</span> pyppeteer import launch</span><br><span class="line"></span><br><span class="line">async def main():</span><br><span class="line">    browser = await launch(<span class="attribute">headless</span>=<span class="literal">False</span>)</span><br><span class="line">   <span class="built_in"> page </span>= await browser.newPage()</span><br><span class="line">    await page.goto(<span class="string">'https://www.taobao.com'</span>)</span><br><span class="line">    await asyncio.sleep(100)</span><br><span class="line"></span><br><span class="line">asyncio.get_event_loop().run_until_complete(main())</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>运行时候进行一下登录，然后就会弹出滑块，自己手动拖动一下，然后就报错了，界面如下： <img src="https://qiniu.cuiqingcai.com/2019-07-26-image-20190416085103827.png" alt=""> 爬虫的时候看到这界面是很让人崩溃的吧，而且这时候我们还发现了页面的 bug，整个浏览器窗口比显示的内容窗口要大，这个是某些页面会出现的情况，让人看起来很不爽。 我们可以先解决一下这个显示的 bug，需要设置下 window-size 还有 viewport，代码如下：</p>
                  <figure class="highlight python">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> pyppeteer <span class="keyword">import</span> launch</span><br><span class="line"></span><br><span class="line">width, height = <span class="number">1366</span>, <span class="number">768</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line">    browser = <span class="keyword">await</span> launch(headless=<span class="literal">False</span>,</span><br><span class="line">                           args=[<span class="string">f'--window-size=<span class="subst">&#123;width&#125;</span>,<span class="subst">&#123;height&#125;</span>'</span>])</span><br><span class="line">    page = <span class="keyword">await</span> browser.newPage()</span><br><span class="line">    <span class="keyword">await</span> page.setViewport(&#123;<span class="string">'width'</span>: width, <span class="string">'height'</span>: height&#125;)</span><br><span class="line">    <span class="keyword">await</span> page.goto(<span class="string">'https://www.taobao.com'</span>)</span><br><span class="line">    <span class="keyword">await</span> asyncio.sleep(<span class="number">100</span>)</span><br><span class="line"></span><br><span class="line">asyncio.get_event_loop().run_until_complete(main())</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这样整个界面就正常了： <img src="https://qiniu.cuiqingcai.com/2019-07-26-image-20190416090302627.png" alt=""> OK，那刚才所说的 webdriver 检测问题怎样来解决呢？其实淘宝主要通过 window.navigator.webdriver 来对 webdriver 进行检测，所以我们只需要使用 JavaScript 将它设置为 false 即可，代码如下：</p>
                  <figure class="highlight python">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> pyppeteer <span class="keyword">import</span> launch</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line">    browser = <span class="keyword">await</span> launch(headless=<span class="literal">False</span>, args=[<span class="string">'--disable-infobars'</span>])</span><br><span class="line">    page = <span class="keyword">await</span> browser.newPage()</span><br><span class="line">    <span class="keyword">await</span> page.goto(<span class="string">'https://login.taobao.com/member/login.jhtml?redirectURL=https://www.taobao.com/'</span>)</span><br><span class="line">    <span class="keyword">await</span> page.evaluate(</span><br><span class="line">        <span class="string">'''() =&gt;&#123; Object.defineProperties(navigator,&#123; webdriver:&#123; get: () =&gt; false &#125; &#125;) &#125;'''</span>)</span><br><span class="line">    <span class="keyword">await</span> asyncio.sleep(<span class="number">100</span>)</span><br><span class="line"></span><br><span class="line">asyncio.get_event_loop().run_until_complete(main())</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这里没加输入用户名密码的代码，当然后面可以自行添加，下面打开之后，我们点击输入用户名密码，然后这时候会出现一个滑动条，这里滑动的话，就可以通过了，如图所示： <img src="https://qiniu.cuiqingcai.com/2019-07-26-image-20190416205859048.png" alt=""> OK，这样的话我们就成功规避了 webdriver 的检测，使用鼠标拖动模拟就可以完成淘宝的登录了。 还有另一种方法可以进一步免去淘宝登录的烦恼，那就是设置用户目录。平时我们已经注意到，当我们登录淘宝之后，如果下次再次打开浏览器发现还是登录的状态。这是因为淘宝的一些关键 Cookies 已经保存到本地了，下次登录的时候可以直接读取并保持登录状态。 那么这些信息保存在哪里了呢？其实就是保存在用户目录下了，里面不仅包含了浏览器的基本配置信息，还有一些 Cache、Cookies 等各种信息都在里面，如果我们能在浏览器启动的时候读取这些信息，那么启动的时候就可以恢复一些历史记录甚至一些登录状态信息了。 这也就解决了一个问题：很多朋友在每次启动 Selenium 或 Pyppeteer 的时候总是是一个全新的浏览器，那就是没有设置用户目录，如果设置了它，每次打开就不再是一个全新的浏览器了，它可以恢复之前的历史记录，也可以恢复很多网站的登录信息。 那么这个怎么来做呢？很简单，在启动的时候设置 userDataDir 就好了，示例如下：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import asyncio</span><br><span class="line"><span class="keyword">from</span> pyppeteer import launch</span><br><span class="line"></span><br><span class="line">async def main():</span><br><span class="line">    browser = await launch(<span class="attribute">headless</span>=<span class="literal">False</span>, <span class="attribute">userDataDir</span>=<span class="string">'./userdata'</span>, args=[<span class="string">'--disable-infobars'</span>])</span><br><span class="line">   <span class="built_in"> page </span>= await browser.newPage()</span><br><span class="line">    await page.goto(<span class="string">'https://www.taobao.com'</span>)</span><br><span class="line">    await asyncio.sleep(100)</span><br><span class="line"></span><br><span class="line">asyncio.get_event_loop().run_until_complete(main())</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>好，这里就是加了一个 userDataDir 的属性，值为 userdata，即当前目录的 userdata 文件夹。我们可以首先运行一下，然后登录一次淘宝，这时候我们同时可以观察到在当前运行目录下又多了一个 userdata 的文件夹，里面的结构是这样子的： <img src="https://qiniu.cuiqingcai.com/2019-07-26-image-20190416211340444.png" alt=""> 具体的介绍可以看官方的一些说明，如：<a href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md" target="_blank" rel="noopener">https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md</a>，这里面介绍了 userdatadir 的相关内容。 再次运行上面的代码，这时候可以发现现在就已经是登录状态了，不需要再次登录了，这样就成功跳过了登录的流程。当然可能时间太久了，Cookies 都过期了，那还是需要登录的。 好了，本想把 Pyppeteer 的用法详细介绍完的，结果只 launch 的方法就介绍这么多了，后面的内容放到其他文章来介绍了，其他的内容后续文章会陆续放出，谢谢。</p>
                  <h2 id="本节代码获取"><a href="#本节代码获取" class="headerlink" title="本节代码获取"></a>本节代码获取</h2>
                  <p>公众号”进击的Coder”回复”Pyppeteer”即可获取本节全部代码。</p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-26 10:46:18" itemprop="dateCreated datePublished" datetime="2019-07-26T10:46:18+08:00">2019-07-26</time>
                </span>
                <span id="/6942.html" class="post-meta-item leancloud_visitors" data-flag-title="Python 爬虫利器之 Pyppeteer 的用法" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>9.6k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>9 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6924.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> 技术杂谈 <i class="label-arrow"></i>
                  </a>
                  <a href="/6924.html" class="post-title-link" itemprop="url">程序员开发必知必会之正则表达式学习资料</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <h2 id="正则表达式30分钟入门教程（https-deerchao-net-tutorials-regex-regex-htm）"><a href="#正则表达式30分钟入门教程（https-deerchao-net-tutorials-regex-regex-htm）" class="headerlink" title="正则表达式30分钟入门教程（https://deerchao.net/tutorials/regex/regex.htm）"></a>正则表达式30分钟入门教程（<a href="https://deerchao.net/tutorials/regex/regex.htm" target="_blank" rel="noopener">https://deerchao.net/tutorials/regex/regex.htm</a>）</h2>
                  <p><img src="https://qiniu.cuiqingcai.com/2019-07-25-232029.png" alt=""></p>
                  <blockquote>
                    <p>本教程目标：30分钟内让你明白正则表达式是什么，并对它有一些基本的了解，让你可以在自己的程序或网页里使用它。</p>
                  </blockquote>
                  <h2 id="正则表达式-必知必会（https-www-zybuluo-com-Yano-note-475174）"><a href="#正则表达式-必知必会（https-www-zybuluo-com-Yano-note-475174）" class="headerlink" title="正则表达式 必知必会（https://www.zybuluo.com/Yano/note/475174）"></a>正则表达式 必知必会（<a href="https://www.zybuluo.com/Yano/note/475174" target="_blank" rel="noopener">https://www.zybuluo.com/Yano/note/475174</a>）</h2>
                  <p><img src="https://qiniu.cuiqingcai.com/2019-07-25-232021.png" alt=""></p>
                  <h2 id="Zjmainstay学习笔记-正则表达式（http-www-zjmainstay-cn-regexp）"><a href="#Zjmainstay学习笔记-正则表达式（http-www-zjmainstay-cn-regexp）" class="headerlink" title="Zjmainstay学习笔记 | 正则表达式（http://www.zjmainstay.cn/regexp）"></a>Zjmainstay学习笔记 | 正则表达式（<a href="http://www.zjmainstay.cn/regexp" target="_blank" rel="noopener">http://www.zjmainstay.cn/regexp</a>）</h2>
                  <p><img src="https://qiniu.cuiqingcai.com/2019-07-25-232013.png" alt=""></p>
                  <h2 id="《精通正则表达式-第三版》"><a href="#《精通正则表达式-第三版》" class="headerlink" title="《精通正则表达式 第三版》"></a>《精通正则表达式 第三版》</h2>
                  <ol>
                    <li>最后，推荐一本动物书《精通正则表达式 第三版》</li>
                  </ol>
                  <blockquote>
                    <p><strong>【顺手提供】</strong>精通正则表达式：第三版 PDF (高清-中文-带标签)</p>
                    <p>关注本公众号<strong>【离不开的网】</strong>，后台回复 “ <strong>正则pdf</strong> ” 即可。</p>
                  </blockquote>
                  <p><img src="https://qiniu.cuiqingcai.com/2019-07-25-231906.png" alt=""></p>
                  <h2 id="相关好文推荐"><a href="#相关好文推荐" class="headerlink" title="相关好文推荐"></a>相关好文推荐</h2>
                  <ol>
                    <li>
                      <p>想精通正则表达式 这几个正则表达式学习资料及工具你必须有 ：<a href="https://www.cnblogs.com/3rocks/p/11212724.html" target="_blank" rel="noopener">https://www.cnblogs.com/3rocks/p/11212724.html</a></p>
                    </li>
                    <li>
                      <p>菜鸟教程-正则表达式 ：<a href="https://www.runoob.com/regexp/regexp-tutorial.html" target="_blank" rel="noopener">https://www.runoob.com/regexp/regexp-tutorial.html</a></p>
                    </li>
                    <li>
                      <p>正则表达式速查表 ：<a href="https://www.jb51.net/article/67634.htm" target="_blank" rel="noopener">https://www.jb51.net/article/67634.htm</a></p>
                    </li>
                    <li>
                      <p>细说python正则表达式 ：<a href="https://www.jianshu.com/p/147fab022566" target="_blank" rel="noopener">https://www.jianshu.com/p/147fab022566</a></p>
                    </li>
                    <li>
                      <p>路人甲的关于正则表达式 ：<a href="https://zhuanlan.zhihu.com/p/21341872?refer=passer" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/21341872?refer=passer</a></p>
                    </li>
                    <li>
                      <p>最全的常用正则表达式大全——包括校验数字、字符、一些特殊的需求等等 ：<a href="http://www.cnblogs.com/zxin/archive/2013/01/26/2877765.html" target="_blank" rel="noopener">http://www.cnblogs.com/zxin/archive/2013/01/26/2877765.html</a></p>
                    </li>
                    <li>
                      <p>深入理解正则表达式 ：<a href="https://www.cnblogs.com/China3S/archive/2013/11/30/3451971.html" target="_blank" rel="noopener">https://www.cnblogs.com/China3S/archive/2013/11/30/3451971.html</a></p>
                    </li>
                  </ol>
                  <blockquote>
                    <p>原文链接：<a href="https://mp.weixin.qq.com/s/CGSUJntKtvOrV1o-R2GrRw" target="_blank" rel="noopener">https://mp.weixin.qq.com/s/CGSUJntKtvOrV1o-R2GrRw</a> 来源公众号：离不开的网</p>
                  </blockquote>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-26 07:22:47" itemprop="dateCreated datePublished" datetime="2019-07-26T07:22:47+08:00">2019-07-26</time>
                </span>
                <span id="/6924.html" class="post-meta-item leancloud_visitors" data-flag-title="程序员开发必知必会之正则表达式学习资料" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>845</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>1 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6921.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> Python <i class="label-arrow"></i>
                  </a>
                  <a href="/6921.html" class="post-title-link" itemprop="url">一看就懂，Python 日志 logging 模块详解及应用</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <h2 id="日志概述"><a href="#日志概述" class="headerlink" title="日志概述"></a>日志概述</h2>
                  <p><strong>百度百科的日志概述</strong>： Windows网络操作系统都设计有各种各样的日志文件，如应用程序日志，安全日志、系统日志、Scheduler服务日志、FTP日志、WWW日志、DNS服务器日志等等，这些根据你的系统开启的服务的不同而有所不同。我们在系统上进行一些操作时，这些日志文件通常会记录下我们操作的一些相关内容，这些内容对系统安全工作人员相当有用。比如说有人对系统进行了IPC探测，系统就会在安全日志里迅速地记下探测者探测时所用的IP、时间、用户名等，用FTP探测后，就会在FTP日志中记下IP、时间、探测所用的用户名等。 <strong>我映像中的日志</strong>： 查看日志是开发人员日常获取信息、排查异常、发现问题的最好途径，日志记录中通常会标记有异常产生的原因、发生时间、具体错误行数等信息，这极大的节省了我们的排查时间，无形中提高了编码效率。</p>
                  <h2 id="日志分类"><a href="#日志分类" class="headerlink" title="日志分类"></a>日志分类</h2>
                  <p>我们可以按照输出终端进行分类，也可以按照日志级别进行分类。输出终端指的是将日志在控制台输出显示和将日志存入文件；日志级别指的是 Debug、Info、WARNING、ERROR以及CRITICAL等严重等级进行划分。</p>
                  <h2 id="Python-的-logging"><a href="#Python-的-logging" class="headerlink" title="Python 的 logging"></a>Python 的 logging</h2>
                  <p>logging提供了一组便利的日志函数，它们分别是：debug()、 info()、 warning()、 error() 和 critical()。logging函数根据它们用来跟踪的事件的级别或严重程度来命名。标准级别及其适用性描述如下（以严重程度递增排序）： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672b380840b4fa6?w=853&amp;h=229&amp;f=png&amp;s=38748" alt=""> 每个级别对应的数字值为 CRITICAL：50，ERROR：40，WARNING：30，INFO：20，DEBUG：10，NOTSET：0。 Python 中日志的默认等级是 WARNING，DEBUG 和 INFO 级别的日志将不会得到显示，在 logging 中更改设置。</p>
                  <h2 id="日志输出"><a href="#日志输出" class="headerlink" title="日志输出"></a>日志输出</h2>
                  <h3 id="输出到控制台"><a href="#输出到控制台" class="headerlink" title="输出到控制台"></a>输出到控制台</h3>
                  <p>使用 logging 在控制台打印日志，这里我们用 Pycharm 编辑器来观察：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import logging</span><br><span class="line"></span><br><span class="line">logging.<span class="builtin-name">debug</span>(<span class="string">'崔庆才丨静觅、韦世东丨奎因'</span>)</span><br><span class="line">logging.<span class="builtin-name">warning</span>(<span class="string">'邀请你关注微信公众号【进击的 Coder】'</span>)</span><br><span class="line">logging.<span class="builtin-name">info</span>(<span class="string">'和大佬一起coding、共同进步'</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bb018ec815a3?w=1206&amp;h=455&amp;f=png&amp;s=103466" alt=""> 从上图运行的结果来看，的确只显示了 WARNING 级别的信息，验证了上面的观点。同时也在控制台输出了日志内容，默认情况下 Python 中使用 logging 模块中的函数打印日志，日志只会在控制台输出，而不会保存到日文件。 <strong>有什么办法可以改变默认的日志级别呢？</strong> 当然是有的，logging 中提供了 basicConfig 让使用者可以适时调节默认日志级别，我们可以将上面的代码改为：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import logging</span><br><span class="line"></span><br><span class="line">logging.basicConfig(<span class="attribute">level</span>=logging.DEBUG)</span><br><span class="line">logging.<span class="builtin-name">debug</span>(<span class="string">'崔庆才丨静觅、韦世东丨奎因'</span>)</span><br><span class="line">logging.<span class="builtin-name">warning</span>(<span class="string">'邀请你关注微信公众号【进击的 Coder】'</span>)</span><br><span class="line">logging.<span class="builtin-name">info</span>(<span class="string">'和大佬一起coding、共同进步'</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bb0f460bc73c?w=1154&amp;h=488&amp;f=png&amp;s=128578" alt=""> 在 basicConfig 中设定 level 参数的级别即可。 思考：如果设定级别为 logging.INFO，那 DEBUG 信息能够显示么？</p>
                  <h3 id="保存到文件"><a href="#保存到文件" class="headerlink" title="保存到文件"></a>保存到文件</h3>
                  <p>刚才演示了如何在控制台输出日志内容，并且自由设定日志的级别，那现在就来看看如何将日志保存到文件。依旧是强大的 basicConfig，我们再将上面的代码改为：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import logging</span><br><span class="line"></span><br><span class="line">logging.basicConfig(<span class="attribute">level</span>=logging.DEBUG, <span class="attribute">filename</span>=<span class="string">'coder.log'</span>, <span class="attribute">filemode</span>=<span class="string">'a'</span>)</span><br><span class="line">logging.<span class="builtin-name">debug</span>(<span class="string">'崔庆才丨静觅、韦世东丨奎因'</span>)</span><br><span class="line">logging.<span class="builtin-name">warning</span>(<span class="string">'邀请你关注微信公众号【进击的 Coder】'</span>)</span><br><span class="line">logging.<span class="builtin-name">info</span>(<span class="string">'和大佬一起coding、共同进步'</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bb1abdf6b05b?w=1320&amp;h=412&amp;f=png&amp;s=100828" alt=""> 在配置中填写 filename （指定文件名） 和 filemode （文件写入方式），控制台的日志输出就不见了，那么 coder.log 会生成么？ <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bb2629d9df1a?w=991&amp;h=236&amp;f=png&amp;s=47054" alt=""> 在 .py 文件的同级目录生成了名为 coder.log 的日志。 通过简单的代码设置，我们就完成了日志文件在控制台和文件中的输出。那既在控制台显示又能保存到文件中呢？</p>
                  <h2 id="强大的-logging"><a href="#强大的-logging" class="headerlink" title="强大的 logging"></a>强大的 logging</h2>
                  <p>logging所提供的模块级别的日志记录函数是对logging日志系统相关类的封装 logging 模块提供了两种记录日志的方式：</p>
                  <ul>
                    <li>使用logging提供的模块级别的函数</li>
                    <li>使用Logging日志系统的四大组件</li>
                  </ul>
                  <p>这里提到的级别函数就是上面所用的 DEBGE、ERROR 等级别，而四大组件则是指 loggers、handlers、filters 和 formatters 这几个组件，下图简单明了的阐述了它们各自的作用： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bb8e26053d22?w=599&amp;h=210&amp;f=png&amp;s=38081" alt=""> 日志器（logger）是入口，真正工作的是处理器（handler），处理器（handler）还可以通过过滤器（filter）和格式器（formatter）对要输出的日志内容做过滤和格式化等处理操作。</p>
                  <h3 id="四大组件"><a href="#四大组件" class="headerlink" title="四大组件"></a>四大组件</h3>
                  <p>下面介绍下与logging四大组件相关的类：Logger, Handler, Filter, Formatter。 <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bd8de061fc84?w=686&amp;h=620&amp;f=png&amp;s=6905" alt=""> <strong>Logger类</strong> Logger 对象有3个工作要做：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="number">1</span>）向应用程序代码暴露几个方法，使应用程序可以在运行时记录日志消息；</span><br><span class="line"><span class="number">2</span>）基于日志严重等级（默认的过滤设施）或filter对象来决定要对哪些日志进行后续处理；</span><br><span class="line"><span class="number">3</span>）将日志消息传送给所有感兴趣的日志handlers。</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>Logger对象最常用的方法分为两类：配置方法 和 消息发送方法 最常用的配置方法如下： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bbaf86c3a29d?w=620&amp;h=167&amp;f=png&amp;s=29759" alt=""> 关于Logger.setLevel()方法的说明： 内建等级中，级别最低的是DEBUG，级别最高的是CRITICAL。例如setLevel(logging.INFO)，此时函数参数为INFO，那么该logger将只会处理INFO、WARNING、ERROR和CRITICAL级别的日志，而DEBUG级别的消息将会被忽略/丢弃。 logger对象配置完成后，可以使用下面的方法来创建日志记录： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bbbf3d172a74?w=689&amp;h=234&amp;f=png&amp;s=34940" alt=""> 那么，怎样得到一个Logger对象呢？一种方式是通过Logger类的实例化方法创建一个Logger类的实例，但是我们通常都是用第二种方式—logging.getLogger()方法。 logging.getLogger()方法有一个可选参数name，该参数表示将要返回的日志器的名称标识，如果不提供该参数，则其值为’root’。若以相同的name参数值多次调用getLogger()方法，将会返回指向同一个logger对象的引用。</p>
                  <figure class="highlight pgsql">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">关于logger的层级结构与有效等级的说明：</span><br><span class="line"></span><br><span class="line">    logger的名称是一个以<span class="string">'.'</span>分割的层级结构，每个<span class="string">'.'</span>后面的logger都是<span class="string">'.'</span>前面的logger的children，例如，有一个名称为 foo 的logger，其它名称分别为 foo.bar, foo.bar.baz 和 foo.bam都是 foo 的后代。</span><br><span class="line">    logger有一个"有效等级（effective level）"的概念。如果一个logger上没有被明确设置一个<span class="keyword">level</span>，那么该logger就是使用它parent的<span class="keyword">level</span>;如果它的parent也没有明确设置<span class="keyword">level</span>则继续向上查找parent的parent的有效<span class="keyword">level</span>，依次类推，直到找到个一个明确设置了<span class="keyword">level</span>的祖先为止。需要说明的是，root logger总是会有一个明确的<span class="keyword">level</span>设置（默认为 <span class="built_in">WARNING</span>）。当决定是否去处理一个已发生的事件时，logger的有效等级将会被用来决定是否将该事件传递给该logger的handlers进行处理。</span><br><span class="line">    child loggers在完成对日志消息的处理后，默认会将日志消息传递给与它们的祖先loggers相关的handlers。因此，我们不必为一个应用程序中所使用的所有loggers定义和配置handlers，只需要为一个顶层的logger配置handlers，然后按照需要创建child loggers就可足够了。我们也可以通过将一个logger的propagate属性设置为<span class="keyword">False</span>来关闭这种传递机制。</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><strong>Handler</strong> Handler对象的作用是（基于日志消息的level）将消息分发到handler指定的位置（文件、网络、邮件等）。Logger对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如，一个应用程序可能想要实现以下几个日志需求：</p>
                  <figure class="highlight lsl">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="number">1</span>）把所有日志都发送到一个日志文件中；</span><br><span class="line"><span class="number">2</span>）把所有严重级别大于等于error的日志发送到stdout（标准输出）；</span><br><span class="line"><span class="number">3</span>）把所有严重级别为critical的日志发送到一个<span class="section">email</span>邮件地址。</span><br><span class="line">这种场景就需要<span class="number">3</span>个不同的handlers，每个handler复杂发送一个特定严重级别的日志到一个特定的位置。</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>一个handler中只有非常少数的方法是需要应用开发人员去关心的。对于使用内建handler对象的应用开发人员来说，似乎唯一相关的handler方法就是下面这几个配置方法： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bbcf0837d1f2?w=608&amp;h=169&amp;f=png&amp;s=27138" alt=""> 需要说明的是，应用程序代码不应该直接实例化和使用Handler实例。因为Handler是一个基类，它只定义了素有handlers都应该有的接口，同时提供了一些子类可以直接使用或覆盖的默认行为。下面是一些常用的Handler： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bbd41c2102cc?w=691&amp;h=433&amp;f=png&amp;s=82087" alt=""> <strong>Formater</strong> Formater对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是，应用代码可以直接实例化Formatter类。另外，如果你的应用程序需要一些特殊的处理行为，也可以实现一个Formatter的子类来完成。 Formatter类的构造方法定义如下：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">logging.Formatter.__init__(<span class="attribute">fmt</span>=None, <span class="attribute">datefmt</span>=None, <span class="attribute">style</span>=<span class="string">'%'</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>该构造方法接收3个可选参数：</p>
                  <ul>
                    <li>fmt：指定消息格式化字符串，如果不指定该参数则默认使用message的原始值</li>
                    <li>datefmt：指定日期格式字符串，如果不指定该参数则默认使用”%Y-%m-%d %H:%M:%S”</li>
                    <li>style：Python 3.2新增的参数，可取值为 ‘%’, ‘{‘和 ‘$’，如果不指定该参数则默认使用’%’</li>
                  </ul>
                  <p><strong>Filter</strong> Filter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类，它只允许某个logger层级下的日志事件通过过滤。该类定义如下：</p>
                  <figure class="highlight pgsql">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">class</span> logging.<span class="keyword">Filter</span>(<span class="type">name</span>=<span class="string">''</span>)</span><br><span class="line">    <span class="keyword">filter</span>(<span class="type">record</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>比如，一个filter实例化时传递的name参数值为’A.B’，那么该filter实例将只允许名称为类似如下规则的loggers产生的日志记录通过过滤：’A.B’，’A.B,C’，’A.B.C.D’，’A.B.D’，而名称为’A.BB’, ‘B.A.B’的loggers产生的日志则会被过滤掉。如果name的值为空字符串，则允许所有的日志事件通过过滤。 filter方法用于具体控制传递的record记录是否能通过过滤，如果该方法返回值为0表示不能通过过滤，返回值为非0表示可以通过过滤。</p>
                  <figure class="highlight pgsql">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">说明：</span><br><span class="line"></span><br><span class="line">    如果有需要，也可以在<span class="keyword">filter</span>(<span class="type">record</span>)方法内部改变该<span class="type">record</span>，比如添加、删除或修改一些属性。</span><br><span class="line">    我们还可以通过<span class="keyword">filter</span>做一些统计工作，比如可以计算下被一个特殊的logger或<span class="keyword">handler</span>所处理的<span class="type">record</span>数量等。</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="实战演练"><a href="#实战演练" class="headerlink" title="实战演练"></a>实战演练</h2>
                  <p>上面文绉绉的说了(复制/粘贴)那么多，现在应该动手实践了。 <strong>现在我需要既将日志输出到控制台、又能将日志保存到文件，我应该怎么办？</strong> 利用刚才所学的知识，我们可以构思一下： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bcba03047ac6?w=806&amp;h=150&amp;f=png&amp;s=11198" alt=""> 看起来好像也不难，挺简单的样子，但是实际如此吗？ 在实际的工作或应用中，我们或许还需要指定文件存放路径、用随机数作为日志文件名、显示具体的信息输出代码行数、日志信息输出日期和日志写入方式等内容。再构思一下： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bd3489169e73?w=1079&amp;h=391&amp;f=png&amp;s=23173" alt=""> 具体代码如下：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import os</span><br><span class="line">import logging</span><br><span class="line">import uuid</span><br><span class="line"><span class="keyword">from</span><span class="built_in"> logging </span>import Handler, FileHandler, StreamHandler</span><br><span class="line"></span><br><span class="line">class PathFileHandler(FileHandler):</span><br><span class="line">    def __init__(self, path, filename, <span class="attribute">mode</span>=<span class="string">'a'</span>, <span class="attribute">encoding</span>=None, <span class="attribute">delay</span>=<span class="literal">False</span>):</span><br><span class="line"></span><br><span class="line">        filename = os.fspath(filename)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(path):</span><br><span class="line">            os.mkdir(path)</span><br><span class="line">        self.baseFilename = os.path.join(path, filename)</span><br><span class="line">        self.mode = mode</span><br><span class="line">        self.encoding = encoding</span><br><span class="line">        self.delay = delay</span><br><span class="line">        <span class="keyword">if</span> delay:</span><br><span class="line">            Handler.__init__(self)</span><br><span class="line">            self.stream = None</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            StreamHandler.__init__(self, self._open())</span><br><span class="line"></span><br><span class="line">class Loggers(object):</span><br><span class="line">    # 日志级别关系映射</span><br><span class="line">    level_relations = &#123;</span><br><span class="line">        <span class="string">'debug'</span>: logging.DEBUG, <span class="string">'info'</span>: logging.INFO, <span class="string">'warning'</span>: logging.WARNING,</span><br><span class="line">        <span class="string">'error'</span>: logging.ERROR, <span class="string">'critical'</span>: logging.CRITICAL</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    def __init__(self, <span class="attribute">filename</span>=<span class="string">'&#123;uid&#125;.log'</span>.format(uid=uuid.uuid4()), <span class="attribute">level</span>=<span class="string">'info'</span>, <span class="attribute">log_dir</span>=<span class="string">'log'</span>,</span><br><span class="line">                 <span class="attribute">fmt</span>=<span class="string">'%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'</span>):</span><br><span class="line">        self.logger = logging.getLogger(filename)</span><br><span class="line">        abspath = os.path.dirname(os.path.abspath(__file__))</span><br><span class="line">        self.directory = os.path.join(abspath, log_dir)</span><br><span class="line">        format_str = logging.Formatter(fmt)  # 设置日志格式</span><br><span class="line">        self.logger.setLevel(self.level_relations.<span class="builtin-name">get</span>(level))  # 设置日志级别</span><br><span class="line">        stream_handler = logging.StreamHandler()  # 往屏幕上输出</span><br><span class="line">        stream_handler.setFormatter(format_str)</span><br><span class="line">        file_handler = PathFileHandler(<span class="attribute">path</span>=self.directory, <span class="attribute">filename</span>=filename, <span class="attribute">mode</span>=<span class="string">'a'</span>)</span><br><span class="line">        file_handler.setFormatter(format_str)</span><br><span class="line">        self.logger.addHandler(stream_handler)</span><br><span class="line">        self.logger.addHandler(file_handler)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line">    txt = <span class="string">"关注公众号【进击的 Coder】，回复『日志代码』可以领取文章中完整的代码以及流程图"</span></span><br><span class="line">    log = Loggers(<span class="attribute">level</span>=<span class="string">'debug'</span>)</span><br><span class="line">    log.logger.<span class="builtin-name">info</span>(4)</span><br><span class="line">    log.logger.<span class="builtin-name">info</span>(5)</span><br><span class="line">    log.logger.<span class="builtin-name">info</span>(txt)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>文件保存后运行，运行结果如下图所示： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bc4eb81ba291?w=1437&amp;h=602&amp;f=png&amp;s=153201" alt=""> 日志确实在控制台输出了，再来看一下目录内是否生成有指定的文件和文件夹： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bc654930ee3f?w=1152&amp;h=161&amp;f=png&amp;s=35731" alt=""> 文件打开后可以看到里面输出的内容： <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bc6cd9fa316a" alt=""> <strong>正确的学习方式是什么</strong> 是一步步的看着文章介绍，等待博主结论？ 是拿着代码运行，跑一遍？ 都不是，应该是一边看着文章，一边拿着示例代码琢磨和研究，到底哪里可以改进、哪里可以设计得更好。如果你需要文章中所用到的示例代码和流程图，那么关注微信公众号【进击的 Coder】，回复『日志代码』就可以领取文章中完整的代码以及流程图。毕竟，学习是一件勤劳的事。 <img src="https://user-gold-cdn.xitu.io/2018/11/19/1672bd719ead7e24" alt=""> 参考资料： <a href="https://www.cnblogs.com/yyds/p/6901864.html" target="_blank" rel="noopener">云游道士博文</a> <a href="https://www.cnblogs.com/nancyzhu/p/8551506.html" target="_blank" rel="noopener">nancy05博文</a></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/韦世东学算法和反爬虫" class="author" itemprop="url" rel="index">韦世东学算法和反爬虫</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-25 14:53:35" itemprop="dateCreated datePublished" datetime="2019-07-25T14:53:35+08:00">2019-07-25</time>
                </span>
                <span id="/6921.html" class="post-meta-item leancloud_visitors" data-flag-title="一看就懂，Python 日志 logging 模块详解及应用" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>6.7k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>6 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6841.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> 技术杂谈 <i class="label-arrow"></i>
                  </a>
                  <a href="/6841.html" class="post-title-link" itemprop="url">七分钟全面了解位运算</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>位运算是我们在编程中常会遇到的操作，但仍然有很多开发者并不了解位运算，这就导致在遇到位运算时会“打退堂鼓”。实际上，位运算并没有那么复杂，只要我们了解其运算基础和运算符的运算规则，就能够掌握位运算的知识。接下来，我们一起学习位运算的相关知识。 程序中的数在计算机内存中都是以二进制的形式存在的，位运算就是直接对整数在内存中对应的二进制位进行操作。</p>
                  <blockquote>
                    <p>注意：本文只讨论整数运算，小数运算不在本文研究之列</p>
                  </blockquote>
                  <h2 id="位运算的基础"><a href="#位运算的基础" class="headerlink" title="位运算的基础"></a>位运算的基础</h2>
                  <p>我们常用的 <code>3</code>， <code>5</code> 等数字是十进制表示，而位运算的基础是二进制。即人类采用十进制，机器采用的是二进制，要深入了解位运算，就需要了解十进制和二进制的转换方法和对应关系。</p>
                  <h3 id="二进制"><a href="#二进制" class="headerlink" title="二进制"></a>二进制</h3>
                  <p>十进制转二进制时，采用“除 2 取余，逆序排列”法：</p>
                  <ol>
                    <li>用 2 整除十进制数，得到商和余数;</li>
                    <li>再用 2 整除商，得到新的商和余数;</li>
                    <li>重复第 1 和第 2 步，直到商为 0;</li>
                    <li>将先得到的余数作为二进制数的高位，后得到的余数作为二进制数的低位，依次排序;</li>
                  </ol>
                  <p>排序结果就是该十进制数的二进制表示。例如十进制数 <code>101</code> 转换为二进制数的计算过程如下：</p>
                  <figure class="highlight basic">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="symbol">101 </span>% <span class="number">2</span> = <span class="number">50</span> 余 <span class="number">1</span></span><br><span class="line"><span class="symbol">50 </span>% <span class="number">2</span> = <span class="number">25</span> 余 <span class="number">0</span></span><br><span class="line"><span class="symbol">25 </span>% <span class="number">2</span> = <span class="number">12</span> 余 <span class="number">1</span></span><br><span class="line"><span class="symbol">12 </span>% <span class="number">2</span> = <span class="number">6</span> 余 <span class="number">0</span></span><br><span class="line"><span class="symbol">6 </span>% <span class="number">2</span> = <span class="number">3</span> 余 <span class="number">0</span></span><br><span class="line"><span class="symbol">3 </span>% <span class="number">2</span> = <span class="number">1</span> 余 <span class="number">1</span></span><br><span class="line"><span class="symbol">1 </span>% <span class="number">2</span> = <span class="number">0</span> 余 <span class="number">1</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>逆序排列即二进制中的从高位到低位排序，得到 <code>7</code> 位二进制数为 <code>1100101</code>，如果要转换为 <code>8</code> 位二进制数，就需要在最高位补 <code>0</code>。即十进制数的 <code>8</code> 位二进制数为 <code>01100101</code>。 其完整过程如下图所示： <img src="https://user-gold-cdn.xitu.io/2019/7/12/16be42ccba578426?w=448&amp;h=206&amp;f=png&amp;s=15358" alt=""> 有网友整理了常见的进制与 ASCII 码对照表，表内容如下： ASCII 控制字符 <img src="https://user-gold-cdn.xitu.io/2019/7/12/16be42d124cccad8?w=716&amp;h=858&amp;f=png&amp;s=110314" alt=""> ASCII 可显示字符 <img src="https://user-gold-cdn.xitu.io/2019/7/12/16be42d36b70664e?w=720&amp;h=836&amp;f=png&amp;s=106105" alt=""></p>
                  <h3 id="补码"><a href="#补码" class="headerlink" title="补码"></a>补码</h3>
                  <p>现在，我们已经了解到二进制与十进制的换算方法，并拥有了进制对照表。但在开始学习位运算符之前，我们还需要了解补码的知识。 数值有正负之分，那么仅有 <code>0</code> 和 <code>1</code> 的二进制如何表示正负呢？ 人们设定，二进制中最高位为 <code>0</code> 代表正，为 <code>1</code> 则代表负。例如 <code>0000 1100</code> 对应的十进制为 <code>12</code>，而 <code>1000 1100</code> 对应的十进制为 <code>\-12</code>。这种表示被称作原码。但新的问题出现了，原本二进制的最高位始终为 <code>0</code>，为了表示正负又多出了 <code>1</code>，在执行运算时就会出错。举个例子，<code>1 + (-2)</code> 的二进制运算如下：</p>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">0000 0001 + 1000 0010 </span><br><span class="line"><span class="section">= 1000 0011</span></span><br><span class="line"><span class="section">= -3</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这显然是有问题的，问题就处在这个代表正负的最高位。接着，人们又弄出了反码（二进制各位置的 <code>0</code> 与 <code>1</code> 互换，例如 <code>0000 1100</code> 的反码为 <code>1111 0011</code>）。此时，运算就会变成这样：</p>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">0000 0001 + 1111 1101</span><br><span class="line"><span class="section">= 1111 1110</span></span><br><span class="line"># 在转换成十进制前，需要再次反码</span><br><span class="line"><span class="section">= 1000 0001 </span></span><br><span class="line"><span class="section">= -1</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这次好像正确了。但它仍然有例外，我们来看一下 <code>1 + (-1)</code>：</p>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">0000 0001 <span class="code">+ 1111 +</span> 1110</span><br><span class="line"><span class="section">= 1111 1111</span></span><br><span class="line"><span class="section">= 1000 0000</span></span><br><span class="line"><span class="section">= -0</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>零是没有正负之分的，为了解决这个问题，就搞出了补码的概念。补码是为了让负数变成能够加的正数，所以 <code>负数的补码= 负数的绝对值取反 + 1</code>，例如 <code>\-1</code> 的补码为：</p>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">\-1 的绝对值 1</span><br><span class="line"><span class="section">= 0000 0001 # 1 的二进制原码</span></span><br><span class="line"><span class="section">= 1111 1110 # 原码取反</span></span><br><span class="line"><span class="section">= 1111 1111 # +1 后得到补码</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><code>\-1</code> 补码推导的完整过程如下图所示： <img src="https://user-gold-cdn.xitu.io/2019/7/12/16be42de18183539?w=480&amp;h=152&amp;f=png&amp;s=14842" alt=""> 反过来，由补码推导原码的过程为 <code>原码 = 补码 - 1，再求反</code>。要注意的是，反码过程中，最高位的值不变，这样才能够保证结果的正负不会出错。例如 <code>1 + (-6)</code> 和 <code>1 + (-9)</code> 的运算过程如下：</p>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"># 1 的补码 + -6 的补码</span><br><span class="line">0000 0001 + 1111 1010</span><br><span class="line"><span class="section">= 1111 1011 # 补码运算结果</span></span><br><span class="line"><span class="section">= 1111 1010 # 对补码减 1，得到反码</span></span><br><span class="line"><span class="section">= 1000 0101 # 反码取反，得到原码</span></span><br><span class="line"><span class="section">= -5 # 对应的十进制</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"># 1 的补码 + -9 的补码</span><br><span class="line">0000 0001 + 1111 0111</span><br><span class="line"><span class="section">= 1111 1000 # 补码运算结果</span></span><br><span class="line"><span class="section">= 1111 0111 # 对补码减 1，得到反码</span></span><br><span class="line"><span class="section">= 1000 1000 # 反码取反，得到原码</span></span><br><span class="line"><span class="section">= -8 # 对应的十进制</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>要注意的是，正数的补码与原码相同，不需要额外运算。也可以说，补码的出现就是为了解决负数运算时的符号问题。</p>
                  <blockquote>
                    <p>人生苦短 我用 Python。 崔庆才|静觅 邀请你关注微信公众号：进击的Coder</p>
                  </blockquote>
                  <h2 id="运算符介绍"><a href="#运算符介绍" class="headerlink" title="运算符介绍"></a>运算符介绍</h2>
                  <p>位运算分为 6 种，它们是：</p>
                  <p>名称</p>
                  <p>符号</p>
                  <p>按位与</p>
                  <p>&amp;</p>
                  <p>按位或</p>
                  <p>|</p>
                  <p>按位异或</p>
                  <p>^</p>
                  <p>按位取反</p>
                  <p>~</p>
                  <p>左移运算</p>
                  <p>&lt;&lt;</p>
                  <p>右移运算</p>
                  <p>>&gt;</p>
                  <h3 id="按位与"><a href="#按位与" class="headerlink" title="按位与"></a>按位与</h3>
                  <p>按位与运算将参与运算的两数对应的二进制位相与，当对应的二进制位均为 <code>1</code> 时，结果位为 <code>1</code>，否则结果位为 <code>0</code>。按位与运算的运算符为 <code>&amp;</code>，参与运算的数以补码方式出现。举个例子，将数字 <code>5</code> 和数字 <code>8</code> 进行按位与运算，其实是将数字 <code>5</code> 对应的二进制 <code>0000 0101</code> 和数字 <code>8</code> 对应的二进制 <code>0000 1000</code> 进行按位与运算，即：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="number">0000</span> <span class="number">0101</span></span><br><span class="line">&amp;</span><br><span class="line"><span class="number">0000</span> <span class="number">1000</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>根据按位与的规则，将各个位置的数进行比对。运算过程如下：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="number">0000</span> <span class="number">0101</span></span><br><span class="line">&amp;</span><br><span class="line"><span class="number">0000</span> <span class="number">1000</span></span><br><span class="line">---- ----</span><br><span class="line"><span class="number">0000</span> <span class="number">0000</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>由于它们对应位置中没有“均为 <code>1</code> ”的情况，所以得到的结果是 <code>0000 0000</code>。数字 <code>5</code> 和 <code>8</code> 按位与运算的完整过程如下图： <img src="https://user-gold-cdn.xitu.io/2019/7/12/16be42e0ef065859?w=745&amp;h=290&amp;f=png&amp;s=27444" alt=""> 将结果换算成十进制，得到 <code>0</code>，即 <code>5&amp;8 = 0</code>。</p>
                  <h3 id="按位或"><a href="#按位或" class="headerlink" title="按位或"></a>按位或</h3>
                  <p>按位或运算将参与运算的两数对应的二进制位相或，只要对应的二进制位中有 <code>1</code>，结果位为 <code>1</code>，否则结果位为 <code>0</code>。按位或运算的运算符为 <code>|</code>，参与运算的数以补码方式出现。举个例子，将数字 <code>3</code> 和数字 <code>7</code> 进行按位或运算，其实是将数字 <code>3</code> 对应的二进制 <code>0000 0011</code>和数字 <code>7</code> 对应的二进制 <code>0000 0111</code> 进行按位或运算，即：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="number">0000</span> <span class="number">0011</span></span><br><span class="line">|</span><br><span class="line"><span class="number">0000</span> <span class="number">0111</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>根据按位或的规则，将各个位置的数进行比对。运算过程如下：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="number">0000</span> <span class="number">0011</span></span><br><span class="line">|</span><br><span class="line"><span class="number">0000</span> <span class="number">0111</span></span><br><span class="line">---- ----</span><br><span class="line"><span class="number">0000</span> <span class="number">0111</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>最终得到的结果为 <code>0000 0111</code>。将结果换算成十进制，得到 <code>7</code>，即 <code>3|7 = 7</code>。</p>
                  <h3 id="按位异或"><a href="#按位异或" class="headerlink" title="按位异或"></a>按位异或</h3>
                  <p>按位异或运算将参与运算的两数对应的二进制位相异或，当对应的二进制位值不同时，结果位为 <code>1</code>，否则结果位为 <code>0</code>。按位异或的运算符为 <code>^</code>，参与运算的数以补码方式出现。举个例子，将数字 <code>12</code> 和数字 <code>7</code> 进行按位异或运算，其实是将数字 <code>12</code> 对应的二进制 <code>0000 1100</code> 和数字 <code>7</code> 对应的二进制 <code>0000 0111</code> 进行按位异或运算，即：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="number">0000</span> <span class="number">1100</span></span><br><span class="line">^</span><br><span class="line"><span class="number">0000</span> <span class="number">0111</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>根据按位异或的规则，将各个位置的数进行比对。运算过程如下：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="number">0000</span> <span class="number">1100</span></span><br><span class="line">^</span><br><span class="line"><span class="number">0000</span> <span class="number">0111</span></span><br><span class="line">---- ----</span><br><span class="line"><span class="number">0000</span> <span class="number">1011</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>最终得到的结果为 <code>0000 1011</code>。将结果换算成十进制，得到 <code>11</code>，即 <code>12^7 = 11</code>。</p>
                  <h3 id="按位取反"><a href="#按位取反" class="headerlink" title="按位取反"></a>按位取反</h3>
                  <p>按位取反运算将二进制数的每一个位上面的 <code>0</code> 换成 <code>1</code>，<code>1</code> 换成 <code>0</code>。按位取反的运算符为 <code>~</code>，参与运算的数以补码方式出现。举个例子，对数字 <code>9</code> 进行按位取反运算，其实是将数字 <code>9</code> 对应的二进制 <code>0000 1001</code> 进行按位取反运算，即：</p>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">～0000 1001</span><br><span class="line"><span class="section">= 0000 1001 # 补码，正数补码即原码</span></span><br><span class="line"><span class="section">= 1111 1010 # 取反</span></span><br><span class="line"><span class="section">= -10</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>最终得到的结果为 <code>\-10</code>。再来看一个例子，<code>\-20</code> 按位取反的过程如下：</p>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">～0001 0100</span><br><span class="line"><span class="section">= 1110 1100 # 补码</span></span><br><span class="line"><span class="section">= 0001 0011 # 取反</span></span><br><span class="line"><span class="section">= 19</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>最终得到的结果为 <code>19</code>。我们从示例中找到了规律，按位取反的结果用数学公式表示： <script type="math/tex">～x = -(x + 1)</script> 我们可以将其套用在 <code>9</code> 和 <code>\-20</code> 上：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">～<span class="number">9</span> = -(<span class="number">9</span> + <span class="number">1</span>) = <span class="number">-10</span></span><br><span class="line">~(<span class="number">-20</span>) = -((<span class="number">-20</span>) + <span class="number">1</span>) = <span class="number">19</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这个规律也可以作用于数字 <code>0</code> 上，即 <code>~0 = -(0 + 1) = -1</code>。</p>
                  <h3 id="左移运算"><a href="#左移运算" class="headerlink" title="左移运算"></a>左移运算</h3>
                  <p>左移运算将数对应的二进位全部向左移动若干位，高位丢弃，低位补 <code>0</code>。左移运算的运算符为 <code>&lt;&lt;</code>。举个例子，将数字 <code>5</code> 左移 <code>4</code> 位，其实是将数字 <code>5</code> 对应的二进制 <code>0000 0101</code> 中的二进位向左移动 <code>4</code> 位，即：</p>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">5 &lt;&lt; 4</span><br><span class="line"><span class="section">= 0000 0101 &lt;&lt; 4</span></span><br><span class="line"><span class="section">= 0101 0000 # 高位丢弃，低位补 0</span></span><br><span class="line"><span class="section">= 80</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>数字 <code>5</code> 左移 <code>4</code> 位的完整运算过程如下图： <img src="https://user-gold-cdn.xitu.io/2019/7/12/16be42e6eddfc892?w=530&amp;h=293&amp;f=png&amp;s=17694" alt=""> 最终结果为 <code>80</code>。这等效于: <script type="math/tex">5 _ (2) ^4</script> 也就是说，左移运算的规律为： <script type="math/tex">x << n = x _ (2) ^ n</script>
                  </p>
                  <h3 id="右移运算"><a href="#右移运算" class="headerlink" title="右移运算"></a>右移运算</h3>
                  <p>右移运算将数对应的二进位全部向右移动若干位。对于左边的空位，如果是正数则补 <code>0</code>，负数可能补 <code>0</code> 或 <code>1</code> （Turbo C 和很多编译器选择补 <code>1</code>）。右移运算的运算符为 <code>\&gt;&gt;</code>。举个例子，将数字 <code>80</code> 右移 <code>4</code> 位，其实是将数字 <code>80</code> 对应的二进制 <code>0101 0000</code> 中的二进位向右移动 <code>4</code> 位，即：</p>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">80 &gt;&gt; 4</span><br><span class="line"><span class="section">= 0101 0000 &gt;&gt; 4</span></span><br><span class="line"><span class="section">= 0000 0101 # 正数补0，负数补1 </span></span><br><span class="line"><span class="section">= 5</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>最终结果为 <code>5</code>。这等效于： <script type="math/tex">80 \\div (2)^4</script> 也就是说，右移运算的规律为： <script type="math/tex">x >> n = x \\div (2) ^ n</script> 要注意的是，不能整除时，取整数。这中除法取整的规则类似于 <code>PYTHON</code> 语言中的地板除。</p>
                  <blockquote>
                    <p>超酷人生 我用 Rust 韦世东|奎因 邀请你关注微信公众号：Rust之禅</p>
                  </blockquote>
                  <h2 id="位运算的应用"><a href="#位运算的应用" class="headerlink" title="位运算的应用"></a>位运算的应用</h2>
                  <p>在掌握了位运算的知识后，我们可以在开发中尝试使用它。坊间一直流传着位运算的效率高，速度快，但从未见过文献证明，所以本文不讨论效率和速度的问题。如果正在阅读文章的你有相关文献，请留言告知，谢谢。 <strong>判断数字奇偶</strong> 通常，我们会通过取余来判断数字是奇数还是偶数。例如判断 <code>101</code> 的奇偶用的方法是：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment"># python</span></span><br><span class="line"><span class="keyword">if</span> 101 % 2:</span><br><span class="line">    <span class="builtin-name">print</span>(<span class="string">'偶数'</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="builtin-name">print</span>(<span class="string">'奇数'</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>我们也可以通过位运算中的按位与来实现奇偶判断，例如：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment"># python</span></span><br><span class="line"><span class="keyword">if</span> 101 &amp; 1:</span><br><span class="line">    <span class="builtin-name">print</span>(<span class="string">'奇数'</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="builtin-name">print</span>(<span class="string">'偶数'</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这是因为奇数的二进制最低位始终为 <code>1</code>，而偶数的二进制最低为始终为 <code>0</code>。所以，无论任何奇数与 <code>1</code> 即 <code>0000 0001</code> 相与得到的都是 <code>1</code>，任何偶数与其相与得到的都是 <code>0</code>。 <strong>变量交换</strong> 在 C 语言中，两个变量的交换必须通过第三个变量来实现。伪代码如下：</p>
                  <figure class="highlight asciidoc">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"># 伪代码</span><br><span class="line">a = 3, b = 5</span><br><span class="line">c = a</span><br><span class="line">a = b</span><br><span class="line">b = a</span><br><span class="line">--------</span><br><span class="line">a = 5, b = 3</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>在 PYTHON 语言中并没有这么麻烦，可以直接交换。对应的 PYTHON 代码如下：</p>
                  <figure class="highlight livecodeserver">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment"># python</span></span><br><span class="line"><span class="keyword">a</span>, b = <span class="number">3</span>, <span class="number">5</span></span><br><span class="line"><span class="keyword">a</span>, b = b, <span class="keyword">a</span></span><br><span class="line">print(<span class="keyword">a</span>, b)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>代码运行结果为 <code>5 3</code>。但大部分编程语言都不支持 PYTHON 这种写法，在这种情况下我们可以通过位运算中的按位异或来实现变量的交换。对应的伪代码如下：</p>
                  <figure class="highlight ini">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment"># 伪代码</span></span><br><span class="line"><span class="attr">a</span> = <span class="number">3</span>, b = <span class="number">5</span></span><br><span class="line"><span class="attr">a</span> = a ^ b</span><br><span class="line"><span class="attr">b</span> = a ^ b</span><br><span class="line"><span class="attr">a</span> = a ^ b</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>最后，<code>a = 5, b = 3</code>。我们可以用 C 语言和 PYTHON 语言进行验证，对应的 PYTHON 代码如下：</p>
                  <figure class="highlight livecodeserver">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment"># python</span></span><br><span class="line"><span class="keyword">a</span>, b = <span class="number">3</span>, <span class="number">5</span></span><br><span class="line"><span class="keyword">a</span> = <span class="keyword">a</span> ^ b</span><br><span class="line">b = <span class="keyword">a</span> ^ b</span><br><span class="line"><span class="keyword">a</span> = <span class="keyword">a</span> ^ b</span><br><span class="line">print(<span class="keyword">a</span>, b)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>代码运行结果为 <code>5 3</code>，说明变量交换成功。对应的 C 代码如下：</p>
                  <figure class="highlight cpp">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> a = <span class="number">3</span>, b = <span class="number">5</span>;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"交换前：a=%d , b=%dn"</span>,a,b);</span><br><span class="line">    a = a^b;</span><br><span class="line">    b = a^b;</span><br><span class="line">    a = a^b;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"交换后：a=%d , b=%dn"</span>,a, b);           </span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>代码运行结果如下：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">交换前：<span class="attribute">a</span>=3 , <span class="attribute">b</span>=5</span><br><span class="line">交换后：<span class="attribute">a</span>=5 , <span class="attribute">b</span>=3</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这说明变量交换成功。 <strong>求 x 与 2 的 n 次方乘积</strong> 设一个数为 <code>x</code>，求 <code>x</code> 与 <code>2</code> 的 <code>n</code> 次方乘积。这用数学来计算都是非常简单的： <script type="math/tex">x * (2) ^ n</script> 在位运算中，要实现这个需求只需要用到左移运算，即 <code>x &lt;&lt; n</code>。 <strong>取 x 的第 k 位</strong> 即取数字 <code>x</code> 对应的二进制的第 <code>k</code> 位上的二进制值。假设数字为 <code>5</code>，其对应的二进制为 <code>0000 0101</code>，取第 <code>k</code> 位二进制值的位运算为 <code>x &gt;&gt; k &amp; 1</code>。我们可以用 PYTHON 代码进行验证：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"># python</span><br><span class="line">x = <span class="number">5</span>  # <span class="number">0000</span> <span class="number">0101</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">8</span>):</span><br><span class="line">    print(x &gt;&gt; i &amp; <span class="number">1</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>代码运行结果如下：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="number">1</span></span><br><span class="line"><span class="number">0</span></span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="number">0</span></span><br><span class="line"><span class="number">0</span></span><br><span class="line"><span class="number">0</span></span><br><span class="line"><span class="number">0</span></span><br><span class="line"><span class="number">0</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这说明位运算的算法是正确的，可以满足我们的需求。 <strong>判断赋值</strong></p>
                  <figure class="highlight gml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">if</span> a == <span class="symbol">x</span>:</span><br><span class="line">    <span class="symbol">x</span> = b</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="symbol">x</span> = a</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>等效于 <code>x = a ^ b ^ x</code>。我们可以通过 PYTHON 代码来验证：</p>
                  <figure class="highlight sas">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"># python</span><br><span class="line">a, b, <span class="meta">x</span> = 6, 9, 6</span><br><span class="line"><span class="meta">if</span> a == <span class="meta">x</span>:</span><br><span class="line">    <span class="meta">x</span> = b</span><br><span class="line"><span class="meta">else</span>:</span><br><span class="line">    <span class="meta">x</span> = a</span><br><span class="line">p<span class="meta">rint(</span>a, b, <span class="meta">x</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>代码运行结果为 <code>699</code>，与之等效的代码如下：</p>
                  <figure class="highlight sas">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"># python</span><br><span class="line">a, b, <span class="meta">x</span> = 6, 9, 6</span><br><span class="line"><span class="meta">x</span> = a ^ b ^ <span class="meta">x</span></span><br><span class="line">p<span class="meta">rint(</span>a, b, <span class="meta">x</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这样就省去了 <code>if else</code> 的判断语句。 <strong>代替地板除</strong> 二分查找是最常用的算法之一，但它有一定的前提条件：二分查找的目标必须采用顺序存储结构，且元素有序排列。例如 PYTHON 中的有序列表。二分查找的最优复杂度为 <code>O(1)</code>，最差时间复杂度为 <code>O(log n)</code>。举个例子，假设我们需要从列表 <code>[1, 3, 5, 6, 7, 8, 12, 22, 23, 43, 65, 76, 90, 543]</code> 中找到指定元素的下标，对应的 PYTHON 代码如下：</p>
                  <figure class="highlight yaml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment"># python</span></span><br><span class="line"><span class="string">def</span> <span class="string">search(lis:</span> <span class="string">list,</span> <span class="attr">x:</span> <span class="string">int)</span> <span class="string">-&gt;</span> <span class="attr">int:</span></span><br><span class="line">    <span class="string">""</span><span class="string">"非递归二分查找</span></span><br><span class="line"><span class="string">    返回指定元素在列表中的索引</span></span><br><span class="line"><span class="string">    -1 代表不存在"</span><span class="string">""</span></span><br><span class="line">    <span class="string">mix_index</span> <span class="string">=</span> <span class="number">0</span></span><br><span class="line">    <span class="string">max_index</span> <span class="string">=</span> <span class="string">len(lis)</span> <span class="bullet">-</span> <span class="number">1</span></span><br><span class="line">    <span class="string">while</span> <span class="string">mix_index</span> <span class="string">&lt;=</span> <span class="attr">max_index:</span></span><br><span class="line">        <span class="string">midpoint</span> <span class="string">=</span> <span class="string">(mix_index</span> <span class="string">+</span> <span class="string">max_index)</span> <span class="string">//</span> <span class="number">2</span></span><br><span class="line">        <span class="string">if</span> <span class="string">lis[midpoint]</span> <span class="string">&lt;</span> <span class="attr">x:</span></span><br><span class="line">            <span class="string">mix_index</span> <span class="string">=</span> <span class="string">mix_index</span> <span class="string">+</span> <span class="number">1</span></span><br><span class="line">        <span class="string">elif</span> <span class="string">lis[midpoint]</span> <span class="string">&gt;</span> <span class="attr">x:</span></span><br><span class="line">            <span class="string">max_index</span> <span class="string">=</span> <span class="string">max_index</span> <span class="bullet">-</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">else:</span></span><br><span class="line">            <span class="string">return</span> <span class="string">midpoint</span></span><br><span class="line">    <span class="string">return</span> <span class="number">-1</span></span><br><span class="line"></span><br><span class="line"><span class="string">lists</span> <span class="string">=</span> <span class="string">[1,</span> <span class="number">3</span><span class="string">,</span> <span class="number">5</span><span class="string">,</span> <span class="number">6</span><span class="string">,</span> <span class="number">7</span><span class="string">,</span> <span class="number">8</span><span class="string">,</span> <span class="number">12</span><span class="string">,</span> <span class="number">22</span><span class="string">,</span> <span class="number">23</span><span class="string">,</span> <span class="number">43</span><span class="string">,</span> <span class="number">65</span><span class="string">,</span> <span class="number">76</span><span class="string">,</span> <span class="number">90</span><span class="string">,</span> <span class="number">543</span><span class="string">]</span></span><br><span class="line"><span class="string">res</span> <span class="string">=</span> <span class="string">search(lists,</span> <span class="number">76</span><span class="string">)</span></span><br><span class="line"><span class="string">print(res)</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>在取列表中间值时使用的语句是 <code>midpoint = (mix_index + max_index) // 2</code>，即地板除，我们可以将其替换为 <code>midpoint = (mix_index + max_index) &gt;&gt; 1</code> 最终得到的结果是相同的。这是因为左移 <code>1</code>位 等效于乘以 <code>2</code>，而右移 <code>1</code> 位等效于除以 <code>2</code>。这样的案例还有很多，此处不再赘述。 至此，我们已经对位运算有了一定的了解，希望你在工作中使用位运算。更多 <code>Saoperation</code> 和知识请扫描下方二维码。 <img src="https://user-gold-cdn.xitu.io/2019/7/12/16be42f3a7eae3fa?w=608&amp;h=304&amp;f=jpeg&amp;s=73490" alt=""></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/韦世东学算法和反爬虫" class="author" itemprop="url" rel="index">韦世东学算法和反爬虫</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-12 12:15:17" itemprop="dateCreated datePublished" datetime="2019-07-12T12:15:17+08:00">2019-07-12</time>
                </span>
                <span id="/6841.html" class="post-meta-item leancloud_visitors" data-flag-title="七分钟全面了解位运算" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>5.4k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>5 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6829.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> Python <i class="label-arrow"></i>
                  </a>
                  <a href="/6829.html" class="post-title-link" itemprop="url">Python3 模拟登录并爬取表格数据</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>本节主要内容有：</p>
                  <ul>
                    <li>通过requests库模拟表单提交</li>
                    <li>通过pandas库提取网页表格</li>
                  </ul>
                  <p>上周五，大师兄发给我一个网址，哭哭啼啼地求我：“去！把这个网页上所有年所有县所有作物的数据全爬下来，存到Access里！” 我看他可怜，勉为其难地挥挥手说：“好嘞，马上就开始！”</p>
                  <h2 id="目标分析"><a href="#目标分析" class="headerlink" title="目标分析"></a>目标分析</h2>
                  <p>大师兄给我的网址是这个：<a href="https://www.ctic.org/crm?tdsourcetag=s_pctim_aiomsg" target="_blank" rel="noopener">https://www.ctic.org/crm?tdsourcetag=s_pctim_aiomsg</a> 打开长这样： <img src="https://qiniu.cuiqingcai.com/2019-07-08-175155.png" alt=""> 根据我学爬虫并不久的经验，通常只要把年月日之类的参数附加到url里面去，然后用<code>requests.get</code>拿到<code>response</code>解析html就完了，所以这次应该也差不多——除了要先想办法获得具体有哪些年份、地名、作物名称，其他部分拿以前的代码稍微改改就能用了，毫无挑战性工作，生活真是太无聊了 点击 <code>View Summary</code> 后出现目标网页长这样 <img src="https://qiniu.cuiqingcai.com/2019-07-08-175159.png" alt=""> 那个大表格的数据就是目标数据了，好像没什么了不起的—— 有点不对劲 目标数据所在网页的网址是这样的：<a href="https://www.ctic.org/crm/?action=result" target="_blank" rel="noopener">https://www.ctic.org/crm/?action=result</a> ，刚刚选择的那些参数并没有作为url的参数啊！网址网页都变了，所以也不是ajax 这和我想象的情况有巨大差别啊</p>
                  <h2 id="尝试获取目标页面"><a href="#尝试获取目标页面" class="headerlink" title="尝试获取目标页面"></a>尝试获取目标页面</h2>
                  <p>让我来康康点击<code>View Summary</code>这个按钮时到底发生了啥：右键<code>View Summary</code>检查是这样： <img src="https://qiniu.cuiqingcai.com/2019-07-08-175219.png" alt=""> 实话说，这是我第一次遇到要提交表单的活儿。以前可能是上天眷顾我，统统<code>get</code>就能搞定，今天终于让我碰上一个<code>post</code>了。 点击<code>View Summary</code>，到DevTools里找network第一条： <img src="https://qiniu.cuiqingcai.com/2019-07-08-175223.png" alt=""> 不管三七二十一，<code>post</code>一下试试看</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import requests</span><br><span class="line"></span><br><span class="line">url = <span class="string">'https://www.ctic.org/crm?tdsourcetag=s_pctim_aiomsg'</span></span><br><span class="line">headers = &#123;<span class="string">'user-agent'</span>: <span class="string">'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '</span></span><br><span class="line">           <span class="string">'AppleWebKit/537.36 (KHTML, like Gecko) '</span></span><br><span class="line">           <span class="string">'Chrome/74.0.3729.131 Safari/537.36'</span>,</span><br><span class="line">           <span class="string">'Host'</span>: <span class="string">'www.ctic.org'</span>&#125;</span><br><span class="line">data = &#123;<span class="string">'_csrf'</span>: <span class="string">'SjFKLWxVVkkaSRBYQWYYCA1TMG8iYR8ReUYcSj04Jh4EBzIdBGwmLw=='</span>,</span><br><span class="line">        <span class="string">'CRMSearchForm[year]'</span>: <span class="string">'2011'</span>,</span><br><span class="line">        <span class="string">'CRMSearchForm[format]'</span>: <span class="string">'Acres'</span>,</span><br><span class="line">        <span class="string">'CRMSearchForm[area]'</span>: <span class="string">'County'</span>,</span><br><span class="line">        <span class="string">'CRMSearchForm[region]'</span>: <span class="string">'Midwest'</span>,</span><br><span class="line">        <span class="string">'CRMSearchForm[state]'</span>: <span class="string">'IL'</span>,</span><br><span class="line">        <span class="string">'CRMSearchForm[county]'</span>: <span class="string">'Adams'</span>,</span><br><span class="line">        <span class="string">'CRMSearchForm[crop_type]'</span>: <span class="string">'All'</span>,</span><br><span class="line">        <span class="string">'summary'</span>: <span class="string">'county'</span>&#125;</span><br><span class="line">response = requests.post(url, <span class="attribute">data</span>=data, <span class="attribute">headers</span>=headers)</span><br><span class="line"><span class="builtin-name">print</span>(response.status_code)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>果不其然，输出<code>400</code>……我猜这就是传说中的<code>cookies</code>在搞鬼吗？《Python3网络爬虫实战》只看到第6章的我不禁有些心虚跃跃欲试呢！ 首先，我搞不清<code>cookies</code>具体是啥，只知道它是用来维持会话的，应该来自于第一次<code>get</code>，搞出来看看先：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">response1 = requests.<span class="builtin-name">get</span>(url, <span class="attribute">headers</span>=headers)</span><br><span class="line"><span class="keyword">if</span> response1.status_code == 200:</span><br><span class="line">    cookies = response1.cookies</span><br><span class="line">    <span class="builtin-name">print</span>(cookies)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>输出：</p>
                  <figure class="highlight fsharp">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">&lt;RequestsCookieJar<span class="meta">[&lt;Cookie PHPSESSID=52asgghnqsntitqd7c8dqesgh6 for www.ctic.org/&gt;, &lt;Cookie _csrf=2571c72a4ca9699915ea4037b967827150715252de98ea2173b162fa376bad33s%3A32%3A%22TAhjwgNo5ElZzV55k3DMeFoc5TWrEmXj%22%3B for www.ctic.org/&gt;]</span>&gt;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>Nah，看不懂，不看不管，直接把它放到<code>post</code>里试试</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">response2 = requests.post(url, <span class="attribute">data</span>=data, <span class="attribute">headers</span>=headers, <span class="attribute">cookies</span>=cookies)</span><br><span class="line"><span class="builtin-name">print</span>(response2.status_code)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>还是<code>400</code>，气氛突然变得有些焦灼，我给你<code>cookies</code>了啊，你还想要啥？！ 突然，我发现一件事：<code>post</code>请求所带的<code>data</code>中那个一开始就显得很可疑的<code>_csrf</code>我仿佛在哪儿见过？ 那个我完全看不懂的<code>cookies</code>里好像就有一个<code>_csrf</code>啊！但是两个<code>_csrf</code>的值很明显结构不一样，试了一下把<code>data</code>里的<code>_csrf</code>换成<code>cookies</code>里的<code>_csrf</code>确实也不行。 但是我逐渐有了一个想法：这个两个<code>_csrf</code>虽然不相等，但是应该是匹配的，我刚刚的<code>data</code>来自浏览器，<code>cookies</code>来自python程序，所以不匹配！ 于是我又点开浏览器的DevTools，Ctrl+F搜索了一下，嘿嘿，发现了： <img src="https://qiniu.cuiqingcai.com/2019-07-08-175237.png" alt=""> 和 <img src="https://qiniu.cuiqingcai.com/2019-07-08-175242.png" alt=""> 这三处。 第一处那里的下一行的<code>csrf_token</code>很明显就是<code>post</code>请求所带的<code>data</code>里的<code>_csrf</code>，另外两个是js里的函数，虽然js没好好学但也能看出来这俩是通过<code>post</code>请求获得州名和县名的，Binggo！一下子解决两个问题。 为了验证我的猜想，我打算先直接用requests获取点击<code>View Summary</code>前的页面的HTML和<code>cookies</code>，将从HTML中提取的<code>csrf_token</code>值作为点击<code>View Summary</code>时<code>post</code>请求的<code>data</code>里的<code>_csrf</code>值，同时附上<code>cookies</code>，这样两处<code>_csrf</code>就应该是匹配的了：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">from</span> lxml import etree</span><br><span class="line">response1 = requests.<span class="builtin-name">get</span>(url, <span class="attribute">headers</span>=headers)</span><br><span class="line">cookies = response1.cookies</span><br><span class="line">html = etree.HTML(response1.text)</span><br><span class="line">csrf_token = html.xpath(<span class="string">'/html/head/meta[3]/@content'</span>)[0]</span><br><span class="line">data.update(&#123;<span class="string">'_csrf'</span>: csrf_token&#125;)</span><br><span class="line">response2 = requests.post(url, <span class="attribute">data</span>=data, <span class="attribute">headers</span>=headers, <span class="attribute">cookies</span>=cookies)</span><br><span class="line"><span class="builtin-name">print</span>(response2.status_code)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>输出<code>200</code>，虽然和Chrome显示的<code>302</code>不一样，但是也表示成功，那就不管了。把<code>response2.text</code>写入html文件打开看是这样： <img src="https://qiniu.cuiqingcai.com/2019-07-08-175252.png" alt=""> Yeah，数据都在！说明我的猜想是对的！那一会再试试我从没用过的<code>requests.Session()</code>维持会话，自动处理<code>cookies</code>。</p>
                  <h2 id="尝试pandas库提取网页表格"><a href="#尝试pandas库提取网页表格" class="headerlink" title="尝试pandas库提取网页表格"></a>尝试pandas库提取网页表格</h2>
                  <p>现在既然已经拿到了目标页面的HTML，那在获取所有年、地区、州名、县名之前，先测试一下<code>pandas.read_html</code>提取网页表格的功能。 <code>pandas.read_html</code>这个函数时在写代码时IDE自动补全下拉列表里瞄到的，一直想试试来着，今天乘机拉出来溜溜：</p>
                  <figure class="highlight reasonml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import pandas <span class="keyword">as</span> pd</span><br><span class="line">df = pd.read<span class="constructor">_html(<span class="params">response2</span>.<span class="params">text</span>)</span><span class="literal">[<span class="number">0</span>]</span></span><br><span class="line">print(df)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>输出： <img src="https://qiniu.cuiqingcai.com/2019-07-08-175300.png" alt=""> Yeah！拿到了，确实比自己手写提取方便，而且数值字符串自动转成数值，优秀！</p>
                  <h2 id="准备所有参数"><a href="#准备所有参数" class="headerlink" title="准备所有参数"></a>准备所有参数</h2>
                  <p>接下来要获取所有年、地区、州名、县名。年份和地区是写死在HTML里的，直接xpath获取： <img src="https://qiniu.cuiqingcai.com/2019-07-08-175307.png" alt=""> 州名、县名根据之前发现的两个js函数，要用<code>post</code>请求来获得，其中州名要根据地区名获取，县名要根据州名获取，套两层循环就行</p>
                  <figure class="highlight pf">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">def new():</span><br><span class="line">    session = requests.Session()</span><br><span class="line">    response = session.get(url=url, headers=headers)</span><br><span class="line">    html = etree.HTML(response.text)</span><br><span class="line">    return session, html</span><br><span class="line"></span><br><span class="line">session, html = new()</span><br><span class="line">years = html.xpath('//*[@id=<span class="string">"crmsearchform-year"</span>]/option/text()')</span><br><span class="line">regions = html.xpath('//*[@id=<span class="string">"crmsearchform-region"</span>]/option/text()')</span><br><span class="line">_csrf = html.xpath('/html/head/meta[<span class="number">3</span>]/@content')[<span class="number">0</span>]</span><br><span class="line">region_state = &#123;&#125;</span><br><span class="line">state_county = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> region <span class="keyword">in</span> regions:</span><br><span class="line">    data = &#123;'region': region, '_csrf': _csrf&#125;</span><br><span class="line">    response = session.post(url_state, data=data)</span><br><span class="line">    html = etree.HTML(response.json())</span><br><span class="line">    region_state[region] = &#123;x: y <span class="keyword">for</span> x, y <span class="keyword">in</span></span><br><span class="line">                            zip(html.xpath('//option/@value'),</span><br><span class="line">                                html.xpath('//option/text()'))&#125;</span><br><span class="line">    <span class="keyword">for</span> <span class="keyword">state</span> <span class="keyword">in</span> region_state[region]:</span><br><span class="line">        data = &#123;'<span class="keyword">state</span>': <span class="keyword">state</span>, '_csrf': _csrf&#125;</span><br><span class="line">        response = session.post(url_county, data=data)</span><br><span class="line">        html = etree.HTML(response.json())</span><br><span class="line">        state_county[<span class="keyword">state</span>] = html.xpath('//option/@value')</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>啧啧，使用<code>requests.Session</code>就完全不需要自己管理<code>cookies</code>了，方便！具体获得的州名县名就不放出来了，实在太多了。然后把所有年、地区、州名、县名的可能组合先整理成csv文件，一会直接从csv里读取并构造<code>post</code>请求的<code>data</code>字典：</p>
                  <figure class="highlight pf">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">remain = [[str(year), str(region), str(<span class="keyword">state</span>), str(county)] </span><br><span class="line">         <span class="keyword">for</span> year <span class="keyword">in</span> years <span class="keyword">for</span> region <span class="keyword">in</span> regions</span><br><span class="line">         <span class="keyword">for</span> <span class="keyword">state</span> <span class="keyword">in</span> region_state[region] <span class="keyword">for</span> county <span class="keyword">in</span> state_county[<span class="keyword">state</span>]]</span><br><span class="line">remain = pd.DataFrame(remain, columns=['CRMSearchForm[year]',</span><br><span class="line">                                       'CRMSearchForm[region]',</span><br><span class="line">                                       'CRMSearchForm[<span class="keyword">state</span>]',</span><br><span class="line">                                       'CRMSearchForm[county]'])</span><br><span class="line">remain.to_csv('remain.csv', index=False)</span><br><span class="line"><span class="comment"># 由于州名有缩写和全称，也本地保存一份</span></span><br><span class="line">import json</span><br><span class="line">with open('region_state.json', 'w') as json_file:</span><br><span class="line">        json.dump(region_state, json_file, indent=<span class="number">4</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>我看了一下，一共49473行——也就是说至少要发送49473个<code>post</code>请求才能爬完全部数据，纯手工获取的话大概要点击十倍这个数字的次数……</p>
                  <h2 id="正式开始"><a href="#正式开始" class="headerlink" title="正式开始"></a>正式开始</h2>
                  <p>那么开始爬咯</p>
                  <figure class="highlight sql">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">import pyodbc</span><br><span class="line"><span class="keyword">with</span> <span class="keyword">open</span>(<span class="string">"region_state.json"</span>) <span class="keyword">as</span> json_file:</span><br><span class="line">    region_state = json.load(json_file)</span><br><span class="line"><span class="keyword">data</span> = pd.read_csv(<span class="string">'remain.csv'</span>)</span><br><span class="line"><span class="comment"># 读取已经爬取的</span></span><br><span class="line">cnxn = pyodbc.connect(<span class="string">'DRIVER=&#123;Microsoft Access Driver (*.mdb, *.accdb)&#125;;'</span></span><br><span class="line">                      <span class="string">'DBQ=./ctic_crm.accdb'</span>)</span><br><span class="line">crsr = cnxn.cursor()</span><br><span class="line">crsr.execute(<span class="string">'select Year_, Region, State, County from ctic_crm'</span>)</span><br><span class="line">done = crsr.fetchall()</span><br><span class="line">done = [<span class="keyword">list</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> done]</span><br><span class="line">done = pd.DataFrame([<span class="keyword">list</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> done], <span class="keyword">columns</span>=[<span class="string">'CRMSearchForm[year]'</span>,</span><br><span class="line">                                                      <span class="string">'CRMSearchForm[region]'</span>,</span><br><span class="line">                                                      <span class="string">'CRMSearchForm[state]'</span>,</span><br><span class="line">                                                      <span class="string">'CRMSearchForm[county]'</span>])</span><br><span class="line">done[<span class="string">'CRMSearchForm[year]'</span>] = done[<span class="string">'CRMSearchForm[year]'</span>].astype(<span class="string">'int64'</span>)</span><br><span class="line">state2st = &#123;y: x <span class="keyword">for</span> z <span class="keyword">in</span> region_state.values() <span class="keyword">for</span> x, y <span class="keyword">in</span> z.items()&#125;</span><br><span class="line">done[<span class="string">'CRMSearchForm[state]'</span>] = [state2st[x]</span><br><span class="line">                                <span class="keyword">for</span> x <span class="keyword">in</span> done[<span class="string">'CRMSearchForm[state]'</span>]]</span><br><span class="line"><span class="comment"># 排除已经爬取的</span></span><br><span class="line">remain = data.append(done)</span><br><span class="line">remain = remain.drop_duplicates(<span class="keyword">keep</span>=<span class="literal">False</span>)</span><br><span class="line">total = <span class="keyword">len</span>(remain)</span><br><span class="line">print(f<span class="string">'&#123;total&#125; left.n'</span>)</span><br><span class="line">del <span class="keyword">data</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># %%</span></span><br><span class="line">remain[<span class="string">'CRMSearchForm[year]'</span>] = remain[<span class="string">'CRMSearchForm[year]'</span>].astype(<span class="string">'str'</span>)</span><br><span class="line"><span class="keyword">columns</span> = [<span class="string">'Crop'</span>,</span><br><span class="line">           <span class="string">'Total_Planted_Acres'</span>,</span><br><span class="line">           <span class="string">'Conservation_Tillage_No_Till'</span>,</span><br><span class="line">           <span class="string">'Conservation_Tillage_Ridge_Till'</span>,</span><br><span class="line">           <span class="string">'Conservation_Tillage_Mulch_Till'</span>,</span><br><span class="line">           <span class="string">'Conservation_Tillage_Total'</span>,</span><br><span class="line">           <span class="string">'Other_Tillage_Practices_Reduced_Till15_30_Residue'</span>,</span><br><span class="line">           <span class="string">'Other_Tillage_Practices_Conventional_Till0_15_Residue'</span>]</span><br><span class="line"><span class="keyword">fields</span> = [<span class="string">'Year_'</span>, <span class="string">'Units'</span>, <span class="string">'Area'</span>, <span class="string">'Region'</span>, <span class="string">'State'</span>, <span class="string">'County'</span>] + <span class="keyword">columns</span></span><br><span class="line"><span class="keyword">data</span> = &#123;<span class="string">'CRMSearchForm[format]'</span>: <span class="string">'Acres'</span>,</span><br><span class="line">        <span class="string">'CRMSearchForm[area]'</span>: <span class="string">'County'</span>,</span><br><span class="line">        <span class="string">'CRMSearchForm[crop_type]'</span>: <span class="string">'All'</span>,</span><br><span class="line">        <span class="string">'summary'</span>: <span class="string">'county'</span>&#125;</span><br><span class="line">headers = &#123;<span class="string">'user-agent'</span>: <span class="string">'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '</span></span><br><span class="line">           <span class="string">'AppleWebKit/537.36 (KHTML, like Gecko) '</span></span><br><span class="line">           <span class="string">'Chrome/74.0.3729.131 Safari/537.36'</span>,</span><br><span class="line">           <span class="string">'Host'</span>: <span class="string">'www.ctic.org'</span>,</span><br><span class="line">           <span class="string">'Upgrade-Insecure-Requests'</span>: <span class="string">'1'</span>,</span><br><span class="line">           <span class="string">'DNT'</span>: <span class="string">'1'</span>,</span><br><span class="line">           <span class="string">'Connection'</span>: <span class="string">'keep-alive'</span>&#125;</span><br><span class="line"><span class="keyword">url</span> = <span class="string">'https://www.ctic.org/crm?tdsourcetag=s_pctim_aiomsg'</span></span><br><span class="line">headers2 = headers.copy()</span><br><span class="line">headers2 = headers2.update(&#123;<span class="string">'Referer'</span>: <span class="keyword">url</span>,</span><br><span class="line">                            <span class="string">'Origin'</span>: <span class="string">'https://www.ctic.org'</span>&#125;)</span><br><span class="line"><span class="keyword">def</span> <span class="keyword">new</span>():</span><br><span class="line">    <span class="keyword">session</span> = requests.Session()</span><br><span class="line">    response = session.get(<span class="keyword">url</span>=<span class="keyword">url</span>, headers=headers)</span><br><span class="line">    html = etree.HTML(response.text)</span><br><span class="line">    _csrf = html.xpath(<span class="string">'/html/head/meta[3]/@content'</span>)[<span class="number">0</span>]</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">session</span>, _csrf</span><br><span class="line"><span class="keyword">session</span>, _csrf = <span class="keyword">new</span>()</span><br><span class="line"><span class="keyword">for</span> _, <span class="keyword">row</span> <span class="keyword">in</span> remain.iterrows():</span><br><span class="line">    temp = dict(<span class="keyword">row</span>)</span><br><span class="line">    data.update(temp)</span><br><span class="line">    data.update(&#123;<span class="string">'_csrf'</span>: _csrf&#125;)</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        try:</span><br><span class="line">            response = session.post(<span class="keyword">url</span>, <span class="keyword">data</span>=<span class="keyword">data</span>, headers=headers2, <span class="keyword">timeout</span>=<span class="number">15</span>)</span><br><span class="line">            break</span><br><span class="line">        <span class="keyword">except</span> <span class="keyword">Exception</span> <span class="keyword">as</span> e:</span><br><span class="line">            session.close()</span><br><span class="line">            print(e)</span><br><span class="line">            print(<span class="string">'nSleep 30s.n'</span>)</span><br><span class="line">            time.sleep(<span class="number">30</span>)</span><br><span class="line">            <span class="keyword">session</span>, _csrf = <span class="keyword">new</span>()</span><br><span class="line">            data.update(&#123;<span class="string">'_csrf'</span>: _csrf&#125;)</span><br><span class="line"></span><br><span class="line">    df = pd.read_html(response.text)[<span class="number">0</span>].dropna(how=<span class="string">'all'</span>)</span><br><span class="line">    df.columns = <span class="keyword">columns</span></span><br><span class="line">    df[<span class="string">'Year_'</span>] = <span class="built_in">int</span>(temp[<span class="string">'CRMSearchForm[year]'</span>])</span><br><span class="line">    df[<span class="string">'Units'</span>] = <span class="string">'Acres'</span></span><br><span class="line">    df[<span class="string">'Area'</span>] = <span class="string">'County'</span></span><br><span class="line">    df[<span class="string">'Region'</span>] = temp[<span class="string">'CRMSearchForm[region]'</span>]</span><br><span class="line">    df[<span class="string">'State'</span>] = region_state[temp[<span class="string">'CRMSearchForm[region]'</span>]][temp[<span class="string">'CRMSearchForm[state]'</span>]]</span><br><span class="line">    df[<span class="string">'County'</span>] = temp[<span class="string">'CRMSearchForm[county]'</span>]</span><br><span class="line">    df = df.reindex(<span class="keyword">columns</span>=<span class="keyword">fields</span>)</span><br><span class="line">    <span class="keyword">for</span> <span class="built_in">record</span> <span class="keyword">in</span> df.itertuples(<span class="keyword">index</span>=<span class="literal">False</span>):</span><br><span class="line">        tuple_record = tuple(<span class="built_in">record</span>)</span><br><span class="line">        sql_insert = f<span class="string">'INSERT INTO ctic_crm VALUES &#123;tuple_record&#125;'</span></span><br><span class="line">        sql_insert = sql_insert.replace(<span class="string">', nan,'</span>, <span class="string">', null,'</span>)</span><br><span class="line">        crsr.execute(sql_insert)</span><br><span class="line">        crsr.commit()</span><br><span class="line">    print(total, row.to_list())</span><br><span class="line">    total -= <span class="number">1</span></span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    print(<span class="string">'Done!'</span>)</span><br><span class="line">    crsr.close()</span><br><span class="line">    cnxn.close()</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>注意中间有个<code>try...except..</code>语句，是因为不定时会发生<code>Connection aborted</code>的错误，有时9000次才断一次，有时一次就断，这也是我加上了<code>读取已经爬取的</code>和<code>排除已经爬取的</code>原因，而且担心被识别出爬虫，把<code>headers</code>写的丰富了一些（好像并没有什么卵用），并且每次断开都暂停个30s并重新开一个会话 <img src="https://qiniu.cuiqingcai.com/2019-07-08-175315.png" alt=""> 然后把程序开着过了一个周末，命令行里终于打出了<code>Done!</code>，到Access里一看有816288条记录，心想：下次试试多线程（进程）和代理池。</p>
                  <hr>
                  <p>周一，我把跑出来的数据发给大师兄，大师兄回我：“好的”。 隔着屏幕我都能感受到滔滔不绝的敬仰和感激之情， 一直到现在，大师兄都感动地说不出话来。</p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/墨大宝" class="author" itemprop="url" rel="index">墨大宝</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-09 01:55:30" itemprop="dateCreated datePublished" datetime="2019-07-09T01:55:30+08:00">2019-07-09</time>
                </span>
                <span id="/6829.html" class="post-meta-item leancloud_visitors" data-flag-title="Python3 模拟登录并爬取表格数据" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>8.2k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>7 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6808.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> JavaScript <i class="label-arrow"></i>
                  </a>
                  <a href="/6808.html" class="post-title-link" itemprop="url">JavaScript API 设计原则详解</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2>
                  <p>本篇博文来自一次公司内部的前端分享，从多个方面讨论了在设计接口时遵循的原则，总共包含了七个大块。系卤煮自己总结的一些经验和教训。本篇博文同时也参考了其他一些文章，相关地址会在后面贴出来。很难做到详尽充实，如果有好的建议或者不对的地方，还望不吝赐教斧正。</p>
                  <h2 id="一、接口的流畅性"><a href="#一、接口的流畅性" class="headerlink" title="一、接口的流畅性"></a>一、接口的流畅性</h2>
                  <p>好的接口是流畅易懂的，他主要体现如下几个方面： 1.简单 操作某个元素的css属性，下面是原生的方法:</p>
                  <figure class="highlight dart">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="built_in">document</span>.<span class="built_in">querySelector</span>(<span class="string">'#id'</span>).style.color = <span class="string">'red'</span>;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>封装之后</p>
                  <figure class="highlight oxygene">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">a</span><span class="params">(<span class="keyword">selector</span>, color)</span> <span class="comment">&#123;</span></span></span><br><span class="line"><span class="function"><span class="comment">  document.querySelector(selector).style.color = color</span></span></span><br><span class="line"><span class="function"><span class="comment">&#125;</span></span></span><br><span class="line"><span class="function"><span class="title">a</span><span class="params">(<span class="string">'#a'</span>, <span class="string">'red'</span>)</span>;</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>从几十个字母长长的一行到简简单单的一个函数调用，体现了api设计原则之一：简单易用。 2.可阅读性 a(‘#a’, ‘red’)是个好函数，帮助我们简单实用地改变某个元素，但问题来了，如果第一次使用该函数的人来说会比较困惑，a函数是啥函数，没有人告诉他。开发接口有必要知道一点，大多数人都是懒惰的（包括卤煮自己），从颜色赋值这个函数来说，虽然少写了代码，但是增加了单词字母的个数，使得它不再好记。每次做这件事情的时候都需要有映射关系： a—-&gt;color. 如果是简单的几个api倒是无所谓，但是通常一套框架都有几十甚至上百的api，映射成本增加会使得<a href="http://www.codeceo.com/" target="_blank" rel="noopener" title="程序员">程序员</a>哥哥崩溃。 我们需要的就是使得接口名称有意义，下面我们改写一下a函数：</p>
                  <figure class="highlight reasonml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">function</span> <span class="keyword">let</span><span class="constructor">SomeElementChangeColor(<span class="params">selector</span>, <span class="params">color</span>)</span> &#123;</span><br><span class="line">  document.query<span class="constructor">SelectorAll(<span class="params">selector</span>, <span class="params">color</span>)</span>.style.color = color; &#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>letSomeElementChangeColor相对于a来说被赋予了现实语言上的意义，任何人都不需要看说明也能知道它的功能。 3.减少记忆成本 我们刚刚的函数太长了，letSomeElementChangeColor虽然减少了映射成本，有了语言上的意义，但是毫无疑问增加了记忆成本。要知道，包括学霸在内，任何人都不喜欢背单词。不仅仅在此处，原生获取dom的api也同样有这个问题： document.getElementsByClassName； document.getElementsByName; document.querySelectorAll;这些api给人的感觉就是单词太长了，虽然他给出的意义是很清晰，然而这种做法是建立在牺牲简易性和简忆性的基础上进行的。于是我们又再次改写这个之前函数</p>
                  <figure class="highlight reasonml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">function</span> set<span class="constructor">Color(<span class="params">selector</span>, <span class="params">color</span>)</span> &#123;</span><br><span class="line"> xxxxxxxxxxxx</span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>在语言意义不做大的变化前提下，缩减函数名称。使得它易读易记易用。 4.可延伸 所谓延伸就是指函数的使用像流水一样按照书写的顺序执行形成执行链条:</p>
                  <figure class="highlight coffeescript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'id'</span>).style.color = <span class="string">'red'</span>;</span><br><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'id'</span>).style.fontSize = <span class="string">'12px'</span>;</span><br><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'id'</span>).style.backgourdColor = <span class="string">'pink'</span>;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>如果我们需要实现像以上有强关联性的业务时，用我们之前的之前的方法是再次封装两个函数 setFontSize, setbackgroundColor; 然后执行它们 setColor(‘id’, ‘red’);setFontSiez(‘id’, ’12px’); setbackgroundColor(‘id’, ‘pink’); 显然，这样的做法没有懒出境界来；id元素每次都需要重新获取，影响性能，失败；每次都需要添加新的方法，失败； 每次还要调用这些方法，还是失败。下面我们将其改写为可以延伸的函数 首先将获取id方法封装成对象,然后再对象的每个方法中返回这个对象：</p>
                  <figure class="highlight qml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getElement</span>(<span class="params">selector</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.style = <span class="built_in">document</span>.querySelecotrAll(selector).style;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">getElement.prototype.color = <span class="function"><span class="keyword">function</span>(<span class="params">color</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.style.color = <span class="built_in">color</span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br><span class="line">getElement.prototype.background = <span class="function"><span class="keyword">function</span>(<span class="params">bg</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.style.backgroundColor = bg;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br><span class="line">getElement.prototype.fontSize = <span class="function"><span class="keyword">function</span>(<span class="params">size</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.style.fontSize = <span class="built_in">size</span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//调用</span></span><br><span class="line"><span class="keyword">var</span> el = <span class="keyword">new</span> getElement(<span class="string">'#id'</span>)</span><br><span class="line">el.color(<span class="string">'red'</span>).background(<span class="string">'pink'</span>).fontSize(<span class="string">'12px'</span>);</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>简单、流畅、易读，它们看起来就像行云流水一样，即在代码性能上得到了提升优化，又在视觉上悦目。后面我们会在参数里面讲到如何继续优化。 所以，大家都比较喜欢用jquery的api，虽然一个$符号并不代表任何现实意义，但简单的符号有利于我们的使用。它体现了以上的多种原则，简单，易读，易记，链式写法，多参处理。 nightmare:</p>
                  <figure class="highlight coffeescript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'id'</span>).style.color = <span class="string">'red'</span>;</span><br><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'id'</span>).style.fontSize = <span class="string">'12px'</span>;</span><br><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'id'</span>).style.backgourdColor = <span class="string">'pink'</span>;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>dream:</p>
                  <figure class="highlight groovy">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">$(<span class="string">'id'</span>).css(&#123;<span class="string">color:</span><span class="string">'red'</span>, <span class="string">fontSize:</span><span class="string">'12px'</span>, <span class="string">backgroundColor:</span><span class="string">'pink'</span>&#125;)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="二、一致性"><a href="#二、一致性" class="headerlink" title="二、一致性"></a>二、一致性</h2>
                  <p>1.接口的一致性 相关的接口保持一致的风格，一整套 API 如果传递一种熟悉和舒适的感觉，会大大减轻开发者对新工具的适应性。 命名这点事：既要短，又要自描述，最重要的是保持一致性 “在计算机科学界只有两件头疼的事：缓存失效和命名问题” — Phil Karlton 选择一个你喜欢的措辞，然后持续使用。选择一种风格，然后保持这种风格。 Nightmare:</p>
                  <figure class="highlight autohotkey">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="built_in">setColor,</span></span><br><span class="line">letBackGround</span><br><span class="line">changefontSize</span><br><span class="line">makedisplay</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>dream:</p>
                  <figure class="highlight jboss-cli">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">set</span>Color;</span><br><span class="line"><span class="keyword">set</span>Background;</span><br><span class="line"><span class="keyword">set</span>FontSize</span><br><span class="line"><span class="keyword">set</span>.<span class="string">........</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>尽量地保持代码风格和命名风格，使别人读你的代码像是阅读同一个人写的文章一样。</p>
                  <h2 id="三、参数的处理"><a href="#三、参数的处理" class="headerlink" title="三、参数的处理"></a>三、参数的处理</h2>
                  <p>1.参数的类型 判断参数的类型为你的程序提供稳定的保障</p>
                  <figure class="highlight qml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment">//我们规定，color接受字符串类型</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setColor</span>(<span class="params">color</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(<span class="keyword">typeof</span> <span class="built_in">color</span> !== <span class="string">'string'</span>) <span class="keyword">return</span>;</span><br><span class="line">dosomething</span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>2.使用json方式传参 使用json的方式传值很多好处，它可以给参数命名，可以忽略参数的具体位置，可以给参数默认值等等 比如下面这种糟糕的情况:</p>
                  <figure class="highlight actionscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span><span class="params">(param1, param2<span class="rest_arg">...............paramN</span>)</span></span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>你必须对应地把每一个参数按照顺序传入，否则你的方法就会偏离你预期去执行，正确的方法是下面的做法。</p>
                  <figure class="highlight actionscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span><span class="params">(json)</span> </span>&#123;</span><br><span class="line"><span class="comment">//为必须的参数设置默认值</span></span><br><span class="line">   <span class="keyword">var</span> <span class="keyword">default</span> = extend(&#123;</span><br><span class="line">    param: <span class="string">'default'</span>,</span><br><span class="line">    param1: <span class="string">'default'</span></span><br><span class="line">    ......</span><br><span class="line">   &#125;,json)</span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这段函数代码，即便你不传任何参数进来，他也会预期运行。因为在声明的时候，你会根据具体的业务预先决定参数的缺省值。</p>
                  <h2 id="四、可扩展性"><a href="#四、可扩展性" class="headerlink" title="四、可扩展性"></a>四、可扩展性</h2>
                  <p>软件设计最重要的原则之一：永远不修改接口，而是去扩展它！可扩展性同时会要求接口的职责单一，多职责的接口很难扩展。 举个栗子：</p>
                  <figure class="highlight qml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment">//需要同时改变某个元素的字体和背景</span></span><br><span class="line"><span class="comment">// Nightmare:</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">set</span>(<span class="params">selector, color</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">document</span>.querySelectroAll(selector).style.color = <span class="built_in">color</span>;</span><br><span class="line">  <span class="built_in">document</span>.querySelectroAll(selector).style.backgroundColor = <span class="built_in">color</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//无法扩展改函数，如果需要再次改变字体的大小的话，只能修改此函数，在函数后面填加改变字体大小的代码</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//Dream</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">set</span>(<span class="params">selector, color</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> el = <span class="built_in">document</span>.querySelectroAll(selector);</span><br><span class="line">  el.style.color = <span class="built_in">color</span>;</span><br><span class="line">  el.style.backgroundColor = <span class="built_in">color</span>;</span><br><span class="line">  <span class="keyword">return</span> el;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//需要设置字体、背景颜色和大小</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setAgain</span> (<span class="params">selector, color, px</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> el = set(selector, <span class="built_in">color</span>)</span><br><span class="line">  el.style.fontSize = px;</span><br><span class="line">  <span class="keyword">return</span> el;</span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>以上只是简单的添加颜色，业务复杂而代码又不是你写的时候，你就必须去阅读之前的代码再修改它，显然是不符合开放-封闭原则的。修改后的function是返回了元素对象，使得下次需要改变时再次得到返回值做处理。 2.this的运用 可扩展性还包括对this的以及call和apply方法的灵活运用：</p>
                  <figure class="highlight actionscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sayBonjour</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  alert(<span class="keyword">this</span>.a)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">obj.a = <span class="number">1</span>;</span><br><span class="line">obj.say = sayBonjour;</span><br><span class="line">obj.say();<span class="comment">//1</span></span><br><span class="line"><span class="comment">//or</span></span><br><span class="line">sayBonjour.call||apply(obj);<span class="comment">//1</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="五、对错误的处理"><a href="#五、对错误的处理" class="headerlink" title="五、对错误的处理"></a>五、对错误的处理</h2>
                  <p>1.预见错误 可以用 类型检测 typeof 或者try…catch。 typeof 会强制检测对象不抛出错误，对于未定义的变量尤其有用。 2.抛出错误 大多数开发者不希望出错了还需要自己去找带对应得代码，最好方式是直接在console中输出，告诉用户发生了什么事情。我们可以用到浏览器为我们提供的api输出这些信息:console.log/warn/error。你还可以为自己的程序留些后路: try…catch。</p>
                  <figure class="highlight javascript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">error</span> (<span class="params">a</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(<span class="keyword">typeof</span> a !== <span class="string">'string'</span>) &#123;</span><br><span class="line">    <span class="built_in">console</span>.error(<span class="string">'param a must be type of string'</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">error</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// some code excucete here maybe throw wrong</span></span><br><span class="line">  &#125;<span class="keyword">catch</span>(ex) &#123;</span><br><span class="line">    <span class="built_in">console</span>.wran(ex);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="六、可预见性"><a href="#六、可预见性" class="headerlink" title="六、可预见性"></a>六、可预见性</h2>
                  <p>可预见性味程序接口提供健壮性，为保证你的代码顺利执行，必须为它考虑到非正常预期的情况。我们看下不可以预见的代码和可预见的代码的区别用之前的setColor</p>
                  <figure class="highlight javascript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment">//nighware</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">set</span>(<span class="params">selector, color</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">document</span>.getElementById(selector).style.color = color;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//dream</span></span><br><span class="line">zepto.init = <span class="function"><span class="keyword">function</span>(<span class="params">selector, context</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> dom</span><br><span class="line">  <span class="comment">// If nothing given, return an empty Zepto collection</span></span><br><span class="line">  <span class="keyword">if</span> (!selector) <span class="keyword">return</span> zepto.Z()</span><br><span class="line">  <span class="comment">// Optimize for string selectors</span></span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> selector == <span class="string">'string'</span>) &#123;</span><br><span class="line">    selector = selector.trim()</span><br><span class="line">    <span class="comment">// If it's a html fragment, create nodes from it</span></span><br><span class="line">    <span class="comment">// Note: In both Chrome 21 and Firefox 15, DOM error 12</span></span><br><span class="line">    <span class="comment">// is thrown if the fragment doesn't begin with &lt;</span></span><br><span class="line">    <span class="keyword">if</span> (selector[<span class="number">0</span>] == <span class="string">'&lt;'</span> &amp;&amp; fragmentRE.test(selector))</span><br><span class="line">      dom = zepto.fragment(selector, <span class="built_in">RegExp</span>.$<span class="number">1</span>, context), selector = <span class="literal">null</span></span><br><span class="line">    <span class="comment">// If there's a context, create a collection on that context first, and select</span></span><br><span class="line">    <span class="comment">// nodes from there</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (context !== <span class="literal">undefined</span>) <span class="keyword">return</span> $(context).find(selector)</span><br><span class="line">    <span class="comment">// If it's a CSS selector, use it to select nodes.</span></span><br><span class="line">    <span class="keyword">else</span> dom = zepto.qsa(<span class="built_in">document</span>, selector)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// If a function is given, call it when the DOM is ready</span></span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span> (isFunction(selector)) <span class="keyword">return</span> $(<span class="built_in">document</span>).ready(selector)</span><br><span class="line">  <span class="comment">// If a Zepto collection is given, just return it</span></span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span> (zepto.isZ(selector)) <span class="keyword">return</span> selector</span><br><span class="line">  <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// normalize array if an array of nodes is given</span></span><br><span class="line">    <span class="keyword">if</span> (isArray(selector)) dom = compact(selector)</span><br><span class="line">    <span class="comment">// Wrap DOM nodes.</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (isObject(selector))</span><br><span class="line">      dom = [selector], selector = <span class="literal">null</span></span><br><span class="line">    <span class="comment">// If it's a html fragment, create nodes from it</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (fragmentRE.test(selector))</span><br><span class="line">      dom = zepto.fragment(selector.trim(), <span class="built_in">RegExp</span>.$<span class="number">1</span>, context), selector = <span class="literal">null</span></span><br><span class="line">    <span class="comment">// If there's a context, create a collection on that context first, and select</span></span><br><span class="line">    <span class="comment">// nodes from there</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (context !== <span class="literal">undefined</span>) <span class="keyword">return</span> $(context).find(selector)</span><br><span class="line">    <span class="comment">// And last but no least, if it's a CSS selector, use it to select nodes.</span></span><br><span class="line">    <span class="keyword">else</span> dom = zepto.qsa(<span class="built_in">document</span>, selector)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// create a new Zepto collection from the nodes found</span></span><br><span class="line">  <span class="keyword">return</span> zepto.Z(dom, selector)</span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>以上是zepto的源码，可以看见，作者在预见传入的参数时做了很多的处理。其实可预见性是为程序提供了若干的入口，无非是一些逻辑判断而已。zepto在这里使用了很多的是非判断，这样做的好处当然是代码比之前更健壮，但同时导致了代码的冗长，不适合阅读。总之，可预见性真正需要你做的事多写一些对位置实物的参数。把外部的检测改为内部检测。是的使用的人用起来舒心放心开心。呐！做人嘛最重要的就是海森啦。</p>
                  <h2 id="七、注释和文档的可读性"><a href="#七、注释和文档的可读性" class="headerlink" title="七、注释和文档的可读性"></a>七、注释和文档的可读性</h2>
                  <p>一个最好的接口是不需要文档我们也会使用它，但是往往接口量一多和业务增加，接口使用起来也会有些费劲。所以接口文档和注释是需要认真书写的。注释遵循简单扼要地原则，给多年后的自己也给后来者看：</p>
                  <figure class="highlight actionscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment">//注释接口，为了演示PPT用</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">commentary</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="comment">//如果你定义一个没有字面意义的变量时，最好为它写上注释：a：没用的变量，可以删除</span></span><br><span class="line">  <span class="keyword">var</span> a;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//在关键和有歧义的地方写上注释，犹如画龙点睛：路由到hash界面后将所有的数据清空结束函数</span></span><br><span class="line">  <span class="keyword">return</span> go.Navigate(<span class="string">'hash'</span>, <span class="function"><span class="keyword">function</span><span class="params">()</span></span>&#123;</span><br><span class="line">    data.clear();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2>
                  <p>推荐markdown语法书写API文档，github御用文档编写语法。简单、快速，代码高亮、话不多说上图 <img src="http://static.codeceo.com/images/2017/06/f0839dcb3fe291d221540d9aef8c89d1.png" alt=""> 卤煮在此也推荐几个在线编辑的网站。诸君可自行前往练习使用。 <a href="https://www.zybuluo.com/mdeditor" target="_blank" rel="noopener">https://www.zybuluo.com/mdeditor</a> <a href="http://mahua.jser.me/" target="_blank" rel="noopener">http://mahua.jser.me/</a></p>
                  <h2 id="参考博文"><a href="#参考博文" class="headerlink" title="参考博文"></a>参考博文</h2>
                  <p><a href="http://top.css88.com/archives/814" target="_blank" rel="noopener">前端头条-javascript的api设计原则</a> 原文：<a href="http://www.cnblogs.com/constantince/p/5580003.html" target="_blank" rel="noopener">http://www.cnblogs.com/constantince/p/5580003.html</a></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-08 03:25:55" itemprop="dateCreated datePublished" datetime="2019-07-08T03:25:55+08:00">2019-07-08</time>
                </span>
                <span id="/6808.html" class="post-meta-item leancloud_visitors" data-flag-title="JavaScript API 设计原则详解" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>7.2k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>7 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6806.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> JavaScript <i class="label-arrow"></i>
                  </a>
                  <a href="/6806.html" class="post-title-link" itemprop="url">介绍 5 个实用的 Ajax 库</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>在这篇文章中，我们将介绍一些用于AJAX调用的最好的JS库，包括jQuery，Axios和Fetch。欢迎查看代码示例！ AJAX是用来对服务器进行异步HTTP调用的一系列web开发技术客户端框架。 AJAX即Asynchronous JavaScript and XML（异步JavaScript和XML）。AJAX曾是web开发界的一个常见名称，许多流行的JavaScript小部件都是使用AJAX构建的。例如，有些特定的用户交互（如按下按钮）会异步调用到服务器，服务器会检索数据并将其返回给客户端——所有这些都不需要重新加载网页。 <img src="http://static.codeceo.com/images/2018/03/ajax-logo.jpg" alt="" title="ajax-logo"></p>
                  <h2 id="AJAX的现代化重新引入"><a href="#AJAX的现代化重新引入" class="headerlink" title="AJAX的现代化重新引入"></a>AJAX的现代化重新引入</h2>
                  <p>JavaScript已经进化了，现在我们使用前端库和/或如React、Angular、Vue等框架构建了动态的网站。AJAX的概念也经历了重大变化，因为现代异步JavaScript调用涉及检索JSON而不是XML。有很多库允许你从客户端应用程序对服务器进行异步调用。有些进入到浏览器标准，有些则有很大的用户基础，因为它们不但灵活而且易于使用。有些支持promises，有些则使用回调。在本文中，我将介绍用于从服务器获取数据的前5个AJAX库。</p>
                  <h2 id="Fetch-API"><a href="#Fetch-API" class="headerlink" title="Fetch API"></a>Fetch API</h2>
                  <p>Fetch API是XMLHttpRequest的现代替代品，用于从服务器检索资源。与XMLHttpRequest不同的是，它具有更强大的功能集和更有意义的命名。基于其语法和结构，Fetch不但灵活而且易于使用。但是，与其他AJAX HTTP库区别开来的是，它具有所有现代Web浏览器的支持。Fetch遵循请求-响应的方法，也就是说，Fetch提出请求并返回解析到Response对象的promise。 你可以传递Request对象来获取，或者，也可以仅传递要获取的资源的URL。下面的示例演示了使用Fetch创建简单的GET请求。</p>
                  <figure class="highlight javascript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">fetch(<span class="string">'https://www.example.com'</span>, &#123;</span><br><span class="line">        method: <span class="string">'get'</span></span><br><span class="line">    &#125;)</span><br><span class="line">    .then(<span class="function"><span class="params">response</span> =&gt;</span> response.json())</span><br><span class="line">    .then(<span class="function"><span class="params">jsonData</span> =&gt;</span> <span class="built_in">console</span>.log(jsonData))</span><br><span class="line">    .catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">            <span class="comment">//error block</span></span><br><span class="line">     &#125;)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>正如你所看到的，Fetch的then方法返回了一个响应对象，你可以使用一系列的then 进行进一步的操作。我使用.json() 方法将响应转换为JSON并将其输出到控制台。 假如你需要POST表单数据或<a href="https://cloudinary.com/blog/file_upload_with_ajax" target="_blank" rel="noopener">使用Fetch创建AJAX文件上传</a>，将会怎么样？此时，除了Fetch之外，你还需要一个输入表单，并使用FormData库来存储表单对象。</p>
                  <figure class="highlight haskell">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="title">var</span> input = document.querySelector('input[<span class="class"><span class="keyword">type</span>="file"]')</span></span><br><span class="line"><span class="title">var</span> <span class="class"><span class="keyword">data</span> = new <span class="type">FormData</span>()</span></span><br><span class="line"><span class="class"><span class="keyword">data</span>.append('<span class="title">file'</span>, <span class="title">input</span>.<span class="title">files</span>[0])</span></span><br><span class="line"><span class="class"><span class="keyword">data</span>.append('<span class="title">user'</span>, '<span class="title">blizzerand'</span>)</span></span><br><span class="line"><span class="title">fetch</span>('/avatars', &#123;</span><br><span class="line">    method: '<span class="type">POST'</span>,</span><br><span class="line">    body: <span class="class"><span class="keyword">data</span></span></span><br><span class="line">&#125;)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>你可以在官方的<a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" target="_blank" rel="noopener">Mozilla web文档</a>中阅读更多关于Fetch API的信息。</p>
                  <h2 id="Axios"><a href="#Axios" class="headerlink" title="Axios"></a>Axios</h2>
                  <p><a href="https://github.com/axios/axios" target="_blank" rel="noopener">Axios</a>是一个基于XMLHttpRequest而构建的现代JavaScript库，用于进行AJAX调用。它允许你从浏览器和服务器发出HTTP请求。此外，它还支持ES6原生的Promise API。Axios的其他突出特点包括：</p>
                  <ul>
                    <li>拦截请求和响应。</li>
                    <li>使用promise转换请求和响应数据。</li>
                    <li>自动转换JSON数据。</li>
                    <li>取消实时请求。</li>
                  </ul>
                  <p>要使用Axios，你需要先安装它。</p>
                  <figure class="highlight cmake">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">npm <span class="keyword">install</span> axios</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>下面是一个演示Axios行动的基本例子。</p>
                  <figure class="highlight scilab">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="comment">// Make a request for a user with a given ID</span></span><br><span class="line">axios.get(<span class="string">'/user?ID=12345'</span>)</span><br><span class="line">  .<span class="keyword">then</span>(<span class="function"><span class="keyword">function</span> <span class="params">(response)</span> &#123;</span></span><br><span class="line">    console.<span class="built_in">log</span>(response);</span><br><span class="line">  &#125;)</span><br><span class="line">  .<span class="keyword">catch</span>(<span class="function"><span class="keyword">function</span> <span class="params">(error)</span> &#123;</span></span><br><span class="line">    console.<span class="built_in">log</span>(<span class="built_in">error</span>);</span><br><span class="line">  &#125;);</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>与Fetch相比，Axios的语法更简单。让我们做一些更复杂的事情，比如我们之前使用Fetch创建的AJAX文件上传器。</p>
                  <figure class="highlight lua">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">var data = new FormData();</span><br><span class="line">   data.append(<span class="string">'foo'</span>, <span class="string">'bar'</span>);</span><br><span class="line">   data.append(<span class="string">'file'</span>, document.getElementById(<span class="string">'file'</span>).files[<span class="number">0</span>]);</span><br><span class="line">   var <span class="built_in">config</span> = &#123;</span><br><span class="line">        onUploadProgress: <span class="function"><span class="keyword">function</span><span class="params">(progressEvent)</span></span> &#123;</span><br><span class="line">          var percentCompleted = Math.round( (progressEvent.<span class="built_in">loaded</span> * <span class="number">100</span>) / progressEvent.total );</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    axios.put(<span class="string">'/upload/server'</span>, data, <span class="built_in">config</span>)</span><br><span class="line">      .<span class="keyword">then</span>(<span class="function"><span class="keyword">function</span> <span class="params">(res)</span></span> &#123;</span><br><span class="line">        <span class="built_in">output</span>.className = <span class="string">'container'</span>;</span><br><span class="line">        <span class="built_in">output</span>.innerHTML = res.data;</span><br><span class="line">      &#125;)</span><br><span class="line">      .catch(<span class="function"><span class="keyword">function</span> <span class="params">(err)</span></span> &#123;</span><br><span class="line">        <span class="built_in">output</span>.className = <span class="string">'container text-danger'</span>;</span><br><span class="line">        <span class="built_in">output</span>.innerHTML = err.message;</span><br><span class="line">      &#125;);</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>Axios更具可读性。Axios也非常受React和Vue等现代库的欢迎。</p>
                  <h2 id="jQuery"><a href="#jQuery" class="headerlink" title="jQuery"></a>jQuery</h2>
                  <p>jQuery曾经是JavaScript中的一个前线库，用于处理从AJAX调用到操纵DOM内容的所有事情。虽然随着其他前端库的“冲击”，其相关性有所降低，但你仍然可以使用jQuery来进行异步调用。 如果你之前使用过jQuery，那么这可能是最简单的解决方案。但是，你将不得不导入整个jQuery库以使用$.ajax方法。虽然这个库有特定于域的方法，例如$.getJSON，$.get和$.post，但是其语法并不像其他的AJAX库那么简单。以下代码用于编写基本的GET请求。</p>
                  <figure class="highlight javascript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">$.ajax(&#123;</span><br><span class="line">  url: <span class="string">'/users'</span>,</span><br><span class="line">  type: <span class="string">"GET"</span>,</span><br><span class="line">  dataType: <span class="string">"json"</span>,</span><br><span class="line">  success: <span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(data);</span><br><span class="line">  &#125;</span><br><span class="line">  fail: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">"Encountered an error"</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>jQuery好的地方在于，如果你有疑问，那么你可以找到大量的支持和文档。我发现了很多使用FormData()和jQuery进行AJAX文件上传的例子。下面是最简单的方法：</p>
                  <figure class="highlight javascript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">var</span> formData = <span class="keyword">new</span> FormData();</span><br><span class="line">formData.append(<span class="string">'file'</span>, $(<span class="string">'#file'</span>)[<span class="number">0</span>].files[<span class="number">0</span>]);</span><br><span class="line">$.ajax(&#123;</span><br><span class="line">       url : <span class="string">'upload.php'</span>,</span><br><span class="line">       type : <span class="string">'POST'</span>,</span><br><span class="line">       data : formData,</span><br><span class="line">       processData: <span class="literal">false</span>,  <span class="comment">// tell jQuery not to process the data</span></span><br><span class="line">       contentType: <span class="literal">false</span>,  <span class="comment">// tell jQuery not to set contentType</span></span><br><span class="line">       success : <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">           <span class="built_in">console</span>.log(data);</span><br><span class="line">           alert(data);</span><br><span class="line">       &#125;</span><br><span class="line">&#125;);</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="SuperAgent"><a href="#SuperAgent" class="headerlink" title="SuperAgent"></a>SuperAgent</h2>
                  <p>SuperAgent是一个轻量级和渐进式的AJAX库，更侧重于可读性和灵活性。SuperAgent还拥有一个温和的学习曲线，不像其他库。它有一个针对Node.js API相同的模块。SuperAgent有一个接受GET、POST、PUT、DELETE和HEAD等方法的请求对象。然后你可以调用.then()，.end()或新的.await()方法来处理响应。例如，以下代码为使用SuperAgent的简单GET请求。</p>
                  <figure class="highlight less">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="selector-tag">request</span></span><br><span class="line">   <span class="selector-class">.post</span>(<span class="string">'/api/pet'</span>)</span><br><span class="line">   <span class="selector-class">.send</span>(&#123; <span class="attribute">name</span>: <span class="string">'Manny'</span>, <span class="attribute">species</span>: <span class="string">'cat'</span> &#125;)</span><br><span class="line">   <span class="selector-class">.set</span>(<span class="string">'X-API-Key'</span>, <span class="string">'foobar'</span>)</span><br><span class="line">   <span class="selector-class">.set</span>(<span class="string">'Accept'</span>, <span class="string">'application/json'</span>)</span><br><span class="line">   <span class="selector-class">.then</span>(function(res) &#123;</span><br><span class="line">      <span class="selector-tag">alert</span>(<span class="string">'yay got '</span> + JSON.stringify(res.body));</span><br><span class="line">   &#125;);</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>如果你想要做更多的事情，比如使用此AJAX库上传文件，那该怎么做呢？ 同样超级easy。</p>
                  <figure class="highlight less">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="selector-tag">request</span></span><br><span class="line">   <span class="selector-class">.post</span>(<span class="string">'/upload'</span>)</span><br><span class="line">   <span class="selector-class">.field</span>(<span class="string">'user[name]'</span>, <span class="string">'Tobi'</span>)</span><br><span class="line">   <span class="selector-class">.field</span>(<span class="string">'user[email]'</span>, <span class="string">'tobi@learnboost.com'</span>)</span><br><span class="line">   <span class="selector-class">.field</span>(<span class="string">'friends[]'</span>, [<span class="string">'loki'</span>, <span class="string">'jane'</span>])</span><br><span class="line">   <span class="selector-class">.attach</span>(<span class="string">'image'</span>, <span class="string">'path/to/tobi.png'</span>)</span><br><span class="line">   <span class="selector-class">.then</span>(callback);</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>如果你有兴趣了解更多<a href="https://visionmedia.github.io/superagent/" target="_blank" rel="noopener">关于SuperAgent的信息</a>，那么它们有一系列很不错的文档来帮助你开始这个旅程。</p>
                  <h2 id="Request——简化的HTTP客户端"><a href="#Request——简化的HTTP客户端" class="headerlink" title="Request——简化的HTTP客户端"></a>Request——简化的HTTP客户端</h2>
                  <p><a href="https://github.com/request/request" target="_blank" rel="noopener">Request库</a>是进行HTTP调用最简单的方法之一。结构和语法与在Node.js中处理请求的方式非常相似。目前，该项目在GitHub上有18K个星，值得一提的是，它是可用的最流行的HTTP库之一。 下面是一个例子：</p>
                  <figure class="highlight javascript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">var</span> request = <span class="built_in">require</span>(<span class="string">'request'</span>);</span><br><span class="line">request(<span class="string">'http://www.google.com'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">error, response, body</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'error:'</span>, error); <span class="comment">// Print the error if one occurred</span></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'statusCode:'</span>, response &amp;&amp; response.statusCode); <span class="comment">// Print the response status code if a response was received</span></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'body:'</span>, body); <span class="comment">// Print the HTML for the Google homepage.</span></span><br><span class="line">&#125;);</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2>
                  <p>从客户端进行HTTP调用在十年前可不是一件容易的事。前端开发人员不得不依赖于难以使用和实现的XMLHttpRequest。现代的库和HTTP客户端使得用户交互、动画、异步文件上传等前端功能变得更加简单。 我个人最喜欢的是Axios，因为我觉得它更易读更赏心悦目。你也可以忠于Fetch，因为它文档化良好且有标准化的解决方案。 你个人最喜欢的AJAX库是哪个？ 欢迎各位分享你的看法。</p>
                  <ul>
                    <li>译文链接：<a href="http://www.codeceo.com/article/5-javascript-ajax-libs.html" target="_blank" rel="noopener">http://www.codeceo.com/article/5-javascript-ajax-libs.html</a></li>
                    <li>英文原文：<a href="https://dzone.com/articles/top-javascript-libraries-for-making-ajax-calls" target="_blank" rel="noopener">Top JavaScript Libraries for Making AJAX Calls</a></li>
                    <li>翻译作者：<a href="http://www.codeceo.com/" target="_blank" rel="noopener">码农网</a> – 小峰</li>
                  </ul>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-08 01:23:09" itemprop="dateCreated datePublished" datetime="2019-07-08T01:23:09+08:00">2019-07-08</time>
                </span>
                <span id="/6806.html" class="post-meta-item leancloud_visitors" data-flag-title="介绍 5 个实用的 Ajax 库" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>4.7k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>4 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6804.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> JavaScript <i class="label-arrow"></i>
                  </a>
                  <a href="/6804.html" class="post-title-link" itemprop="url">JavaScript 实用窍门总结</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <blockquote>
                    <p>简评：一开始 JavaScript 只是为网页增添一些实时动画效果，现在 JS 已经能做到前后端通吃了，而且还是年度流行语言。本文分享几则 JS 小窍门，可以让你事半功倍 ~</p>
                  </blockquote>
                  <h2 id="1-删除数组尾部元素"><a href="#1-删除数组尾部元素" class="headerlink" title="1. 删除数组尾部元素"></a>1. 删除数组尾部元素</h2>
                  <p>一个简单方法就是改变数组的length值:</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">11</span>, <span class="number">22</span>, <span class="number">33</span>, <span class="number">44</span>, <span class="number">55</span>, <span class="number">66</span>];</span><br><span class="line"><span class="comment">// truncanting</span></span><br><span class="line">arr.length = <span class="number">3</span>;</span><br><span class="line">console.log(arr); <span class="comment">//=&gt; [11, 22, 33]</span></span><br><span class="line"><span class="comment">// clearing</span></span><br><span class="line">arr.length = <span class="number">0</span>;</span><br><span class="line">console.log(arr); <span class="comment">//=&gt; []</span></span><br><span class="line">console.log(arr[<span class="number">2</span>]); <span class="comment">//=&gt; undefined</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="2-使用对象解构（object-destructuring）来模拟命名参数"><a href="#2-使用对象解构（object-destructuring）来模拟命名参数" class="headerlink" title="2. 使用对象解构（object destructuring）来模拟命名参数"></a>2. 使用对象解构（object destructuring）来模拟命名参数</h2>
                  <p>如果需要将一系列可选项作为参数传入函数，你很可能会使用对象（Object）来定义配置（Config）。</p>
                  <figure class="highlight actionscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">doSomething(&#123; foo: <span class="string">'Hello'</span>, bar: <span class="string">'Hey!'</span>, baz: <span class="number">42</span> &#125;);</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">doSomething</span><span class="params">(config)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> foo = config.foo !== <span class="literal">undefined</span> ? config.foo : <span class="string">'Hi'</span>;</span><br><span class="line">  <span class="keyword">const</span> bar = config.bar !== <span class="literal">undefined</span> ? config.bar : <span class="string">'Yo!'</span>;</span><br><span class="line">  <span class="keyword">const</span> baz = config.baz !== <span class="literal">undefined</span> ? config.baz : <span class="number">13</span>;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>不过这是一个比较老的方法了，它模拟了 JavaScript 中的命名参数。 在 ES 2015 中，你可以直接使用对象解构：</p>
                  <figure class="highlight reasonml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">function</span> <span class="keyword">do</span><span class="constructor">Something(&#123; <span class="params">foo</span> = 'Hi', <span class="params">bar</span> = 'Yo!', <span class="params">baz</span> = 13 &#125;)</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>让参数可选也很简单：</p>
                  <figure class="highlight reasonml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">function</span> <span class="keyword">do</span><span class="constructor">Something(&#123; <span class="params">foo</span> = 'Hi', <span class="params">bar</span> = 'Yo!', <span class="params">baz</span> = 13 &#125; = &#123;&#125;)</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="3-使用对象解构来处理数组"><a href="#3-使用对象解构来处理数组" class="headerlink" title="3. 使用对象解构来处理数组"></a>3. 使用对象解构来处理数组</h2>
                  <p>可以使用对象解构的语法来获取数组的元素：</p>
                  <figure class="highlight stata">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">const</span> csvFileLine = '1997,John <span class="keyword">Doe</span>,<span class="keyword">US</span>,john@<span class="keyword">doe</span>.com,New York';</span><br><span class="line"><span class="keyword">const</span> &#123; 2: country, 4: state &#125; = csvFileLine.<span class="keyword">split</span>(',');</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="4-在-Switch-语句中使用范围值"><a href="#4-在-Switch-语句中使用范围值" class="headerlink" title="4. 在 Switch 语句中使用范围值"></a>4. 在 Switch 语句中使用范围值</h2>
                  <p>可以这样写满足范围值的语句：</p>
                  <figure class="highlight javascript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getWaterState</span>(<span class="params">tempInCelsius</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> state;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">switch</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> (tempInCelsius &lt;= <span class="number">0</span>): </span><br><span class="line">      state = <span class="string">'Solid'</span>;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> (tempInCelsius &gt; <span class="number">0</span> &amp;&amp; tempInCelsius &lt; <span class="number">100</span>): </span><br><span class="line">      state = <span class="string">'Liquid'</span>;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>: </span><br><span class="line">      state = <span class="string">'Gas'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> state;</span><br><span class="line">&#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="5-await-多个-async-函数"><a href="#5-await-多个-async-函数" class="headerlink" title="5. await 多个 async 函数"></a>5. await 多个 async 函数</h2>
                  <p>在使用 async/await 的时候，可以使用 Promise.all 来 await 多个 async 函数</p>
                  <figure class="highlight css">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="selector-tag">await</span> <span class="selector-tag">Promise</span><span class="selector-class">.all</span>(<span class="selector-attr">[anAsyncCall(), thisIsAlsoAsync(), oneMore()]</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="6-创建-Pure-objects"><a href="#6-创建-Pure-objects" class="headerlink" title="6. 创建 Pure objects"></a>6. 创建 <a href="http://www.codeceo.com/article/yahoo-pure-css.html" target="_blank" rel="noopener" title="Pure">Pure</a> objects</h2>
                  <p>你可以创建一个 100% pure object，它不从Object中继承任何属性或则方法（比如constructor, toString()等）</p>
                  <figure class="highlight delphi">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">const</span> pureObject = <span class="keyword">Object</span>.create(null);</span><br><span class="line">console.log(pureObject); <span class="comment">//=&gt; &#123;&#125;</span></span><br><span class="line">console.log(pureObject<span class="function">.<span class="keyword">constructor</span>);</span> <span class="comment">//=&gt; undefined</span></span><br><span class="line">console.log(pureObject.toString); <span class="comment">//=&gt; undefined</span></span><br><span class="line">console.log(pureObject.hasOwnProperty); <span class="comment">//=&gt; undefined</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="7-格式化-JSON-代码"><a href="#7-格式化-JSON-代码" class="headerlink" title="7. 格式化 JSON 代码"></a>7. 格式化 JSON 代码</h2>
                  <p>JSON.stringify除了可以将一个对象字符化，还可以格式化输出 JSON 对象</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">const</span> obj = &#123; </span><br><span class="line">  foo: &#123; bar: [<span class="number">11</span>, <span class="number">22</span>, <span class="number">33</span>, <span class="number">44</span>], baz: &#123; bing: <span class="literal">true</span>, boom: <span class="string">'Hello'</span> &#125; &#125; </span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// The third parameter is the number of spaces used to </span></span><br><span class="line"><span class="comment">// beautify the JSON output.</span></span><br><span class="line">JSON.<span class="built_in">string</span>ify(obj, <span class="literal">null</span>, <span class="number">4</span>); </span><br><span class="line"><span class="comment">// =&gt;"&#123;</span></span><br><span class="line"><span class="comment">// =&gt;    "foo": &#123;</span></span><br><span class="line"><span class="comment">// =&gt;        "bar": [</span></span><br><span class="line"><span class="comment">// =&gt;            11,</span></span><br><span class="line"><span class="comment">// =&gt;            22,</span></span><br><span class="line"><span class="comment">// =&gt;            33,</span></span><br><span class="line"><span class="comment">// =&gt;            44</span></span><br><span class="line"><span class="comment">// =&gt;        ],</span></span><br><span class="line"><span class="comment">// =&gt;        "baz": &#123;</span></span><br><span class="line"><span class="comment">// =&gt;            "bing": true,</span></span><br><span class="line"><span class="comment">// =&gt;            "boom": "Hello"</span></span><br><span class="line"><span class="comment">// =&gt;        &#125;</span></span><br><span class="line"><span class="comment">// =&gt;    &#125;</span></span><br><span class="line"><span class="comment">// =&gt;&#125;"</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="8-从数组中移除重复元素"><a href="#8-从数组中移除重复元素" class="headerlink" title="8. 从数组中移除重复元素"></a>8. 从数组中移除重复元素</h2>
                  <p>通过使用集合语法和 Spread 操作，可以很容易将重复的元素移除：</p>
                  <figure class="highlight dart">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">const</span> removeDuplicateItems = arr =&gt; [...<span class="keyword">new</span> <span class="built_in">Set</span>(arr)];</span><br><span class="line">removeDuplicateItems([<span class="number">42</span>, <span class="string">'foo'</span>, <span class="number">42</span>, <span class="string">'foo'</span>, <span class="keyword">true</span>, <span class="keyword">true</span>]);</span><br><span class="line"><span class="comment">//=&gt; [42, "foo", true]</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h2 id="9-平铺多维数组"><a href="#9-平铺多维数组" class="headerlink" title="9. 平铺多维数组"></a>9. 平铺多维数组</h2>
                  <p>使用 Spread 操作平铺嵌套多维数组：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">11</span>, [<span class="number">22</span>, <span class="number">33</span>], [<span class="number">44</span>, <span class="number">55</span>], <span class="number">66</span>];</span><br><span class="line"><span class="keyword">const</span> flatArr = [].concat(...arr); <span class="comment">//=&gt; [11, 22, 33, 44, 55, 66]</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>不过上面的方法仅适用于二维数组，但是通过递归，就可以平铺任意维度的嵌套数组了：</p>
                  <figure class="highlight reasonml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">function</span> flatten<span class="constructor">Array(<span class="params">arr</span>)</span> &#123;</span><br><span class="line">  const flattened = <span class="literal">[]</span>.concat(...arr);</span><br><span class="line">  return flattened.some(item =&gt; <span class="module-access"><span class="module"><span class="identifier">Array</span>.</span></span>is<span class="constructor">Array(<span class="params">item</span>)</span>) ? </span><br><span class="line">    flatten<span class="constructor">Array(<span class="params">flattened</span>)</span> : flattened;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">const arr = <span class="literal">[<span class="number">11</span>, [<span class="number">22</span>, <span class="number">33</span>]</span>, <span class="literal">[<span class="number">44</span>, [<span class="number">55</span>, <span class="number">66</span>, [<span class="number">77</span>, [<span class="number">88</span>]</span>], <span class="number">99</span>]]];</span><br><span class="line">const flatArr = flatten<span class="constructor">Array(<span class="params">arr</span>)</span>; </span><br><span class="line"><span class="comment">//=&gt; [11, 22, 33, 44, 55, 66, 77, 88, 99]</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>希望这些小技巧能帮助你写好 JavaScript ~ 原文：<a href="https://zhuanlan.zhihu.com/p/37493249" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/37493249</a></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-08 01:20:57" itemprop="dateCreated datePublished" datetime="2019-07-08T01:20:57+08:00">2019-07-08</time>
                </span>
                <span id="/6804.html" class="post-meta-item leancloud_visitors" data-flag-title="JavaScript 实用窍门总结" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>2.9k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>3 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6801.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> 技术杂谈 <i class="label-arrow"></i>
                  </a>
                  <a href="/6801.html" class="post-title-link" itemprop="url">知识付费的发展趋势</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p><img src="https://img0.tuicool.com/n2MFZbv.jpg!web" alt=""> 现代人都普遍比较焦虑，越来越多的人感到生活压力很大，在一二线城市不少白领人士更是已是患上“知识焦虑症”：这年头知识信息迭代太快，学习跟不上，别人懂的自己都不懂，总感觉自己欠缺的很多，没有技术和专长不知未来靠什么生存，担心自己长期落后他人从而被社会淘汰，从而产生了一种对未来不确定的心理恐惧。 于是，他们就急于渴求，希望能在短时间内学习大量知识希望从根本上解决自己的实际问题，拼命利用碎片化时间学习补充欠缺知识，不断完善自己的知识体系。这时知识付费的出现，正好满足了他们这些刚性需求。 再加上2018年前以逻辑思维为主一批内容供应商到处鼓吹和贩卖知识焦虑，在这种风气影响下连身边的很多小伙伴都购买了大量知识付费产品，但一年下来却发现这些知识根本没有学会几个，内容良莠不齐，服务跟不上，就算能坚持学完下来还是什么都不会，焦虑依然存在 。甚至更多人觉得其实知识付费就是一场骗局，从本质上来说知识付费并没有解决任何的问题，表面上只是缓解了一些焦虑，在短时间内让一些人自我感觉良好，实则上只是自欺欺人而已。 于是，一些感觉被欺骗的用户纷纷发声，对某些知识付费大号和机构更是愤怒抨击，在这些负面影响下这两年知识付费其实已逐渐降温并遭遇爆冷，甚至还有一些负面声音在说： <strong>在线知识付费的寒冬是不是要来临了？</strong> 其实不然，纵观当下市场发展态势知识付费的前景仍然是广阔的，竞争激烈程度仍方兴未艾，当人们对知识的盲目消费退烧后，知识付费更多的是回归服务价值， <strong>核心需求有两方面</strong> ： 一方面是为了提升欠缺的知识让自己具有更多的市场竞争力，另一方面也希望自己花钱买的知识付费产品是一些真正有帮助的东西，可帮自己迎来一个不错的未来。所以，知识付费的下一阶段必然聚焦于如何让学习用户获得真正有效的学习效果。 那么，知识付费下半场风口到底会吹去哪？ 好学豚认为这一领域的大方向以后的发展趋势大致有如下特征：</p>
                  <h4 id="趋势一：内容下沉，回归有价值的真实内容"><a href="#趋势一：内容下沉，回归有价值的真实内容" class="headerlink" title="趋势一：内容下沉，回归有价值的真实内容"></a>趋势一：内容下沉，回归有价值的真实内容</h4>
                  <h4 id="1、职场技能"><a href="#1、职场技能" class="headerlink" title="1、职场技能"></a>1、职场技能</h4>
                  <p>在知识付费领域和在整个教育市场体系里，职场技能都是最刚需的内容板块，从最早的电算会计化到后来的英语类培训，再到如今一些以工具为代表的设计、办公技能、IT编程等等培训，无一不彰显着其旺盛的生命力。 究其根本原因，这是因为它能够切实解决职场人士的实际需求，所以职场技能内容才会一直火爆，繁荣至今仍在蓬勃发展。</p>
                  <h4 id="2、知识拓展"><a href="#2、知识拓展" class="headerlink" title="2、知识拓展"></a>2、知识拓展</h4>
                  <p>知识拓展是近年来兴起的版块内容，也是互联网上传播特别广泛的内容。以得到为代表，邀请行业细分领域的专业人士，按照标准的生产流程来生产较高质量的内容，帮助拓广知识的视野从而实现专家指路的效果。不容忽视的是，知识拓展的内容从今天来看存在较大的局限，很难解决用户的实际问题，学习效果也很难得到保证；如何打磨出更加直击用户痛点的产品，需要更多的探索和尝试。</p>
                  <h4 id="3、兴趣爱好"><a href="#3、兴趣爱好" class="headerlink" title="3、兴趣爱好"></a>3、兴趣爱好</h4>
                  <p>在线教育领域，兴趣爱好的内容已经有了长足发展。用户的痛点正在逐渐从追求高大上、财务自由这些虚伪的概念转型到享受生活：提 升生活趣味让自己活得更舒服。兴趣爱好内容之所以能生命力如此旺盛，最大因素要得益于其以兴趣为导向，学习者本身并不会有太高的学习结果诉求，更多的是在学习之中获得的乐趣以及对授课老师的认同。</p>
                  <h4 id="趋势二：后续服务提升，注重全流程效果"><a href="#趋势二：后续服务提升，注重全流程效果" class="headerlink" title="趋势二：后续服务提升，注重全流程效果"></a>趋势二：后续服务提升，注重全流程效果</h4>
                  <p>今后知识付费需要做好三个关键点的平衡：碎片化学习、学习规模和学习效果，这三者缺一不可。根据业内行家的实践和推断，下一阶段知识付费的标配服务模式将会是1+1+N模式，能较好地实现以上三个关键点的平衡。 第一个“1”代表的是标准化的课程： 这个课程将会以“图文”、“视频”或“音频”形式进行展现，让学习者完成自学。这个“1”满足了碎片化学习的随时随地性，给予了学习者最大限度的自由度，同时也满足了由于学习规模日益扩大而带来的各种问题。 第二个“1”代表的是在线训练营： 就是把线下的集中学习模式搬到线上，开展一段时间的在线集中训练，通过学习流程、学习氛围、学习评估的三位一体运作，从而使学习效果得以达成。总体做法是把所有参与训练营的学员，分成不同的小组，然后配定相应老师。每个学员在学习过程中都需要完成相应作业，然后交给老师进行批改和点评。整个训练营阶段会有实战练习，根据学习者的自身情况或使用真实的案例来进行实战，进而帮助学习者实现学以致用。同时，老师需要对每个学习者进行阶段性评分，在学习完成时要进行总体评估；老师还需要针对小组学习效果进行展示和相应氛围的营造。 最后一个“N”则代表通过科技的力量，实现多种教学辅助手段 ：比如老师和学员之间的交互，老师批量布置和批改作业，对学员进行评分和评估，给积极学习者一些奖励等等，所有这些辅助手段，将会让老师和学员之间的交互更紧密，让学习效率更高、让学习流程更完整、学习模式更人性，从而最终达到提升学习效果的目的。</p>
                  <h4 id="趋势三：更多垂直领域的小KOL将通过工具类平台输出产品"><a href="#趋势三：更多垂直领域的小KOL将通过工具类平台输出产品" class="headerlink" title="趋势三：更多垂直领域的小KOL将通过工具类平台输出产品"></a>趋势三：更多垂直领域的小KOL将通过工具类平台输出产品</h4>
                  <p>垂直领域的小KOL，他们标签清晰、价值点明确，用户深度认可，反倒更容易突围，甚至可以脱离平台，通过好学豚这类工具就可以完成知识店铺的搭建。而且因为他们有很强的用户沉淀能力，所以也更有动力参与。长期来看，小KOL通过线上虚拟产品输出知识将会成为一个新的趋势。</p>
                  <h4 id="趋势四：一线城市受众有限，二、三线城市女性用户价值开始受到重视"><a href="#趋势四：一线城市受众有限，二、三线城市女性用户价值开始受到重视" class="headerlink" title="趋势四：一线城市受众有限，二、三线城市女性用户价值开始受到重视"></a>趋势四：一线城市受众有限，二、三线城市女性用户价值开始受到重视</h4>
                  <p>一线城市对于知识付费产品的承载能力是有限的，据观察主流付费用户都不是一线白领，而是二三线城市的宝妈。她们更愿意在知识上付钱，也愿意分享课程，赚取一定分成。 互联网独角兽的发展规律一再地证明二三线城市的女性是主力消费群体，知识付费也不会逃离这个规律，现在很多母婴类课程、女性个人提升课程、陪伴式励志课程、情商人际关系类课程销量都很不错。这是一个巨大的市场空窗，目前市场上的多数知识付费平台还忽视了二三线城市的用户，电商平台和垂直大号也还没有真正介入这一行业，这个空窗很有可能下一家知识付费独角兽企业崛起的机会。 针对以上趋势，好学豚知识付费平台也正在进行多方面的尝试，希望能把握住这些新的市场机会迎来爆发性增长，对于当下的知识付费从业者来说既是一个巨大的挑战，也是突围而出成为新独角兽企业的机会。 原文：<a href="https://www.iyiou.com/p/104850.html" target="_blank" rel="noopener">https://www.iyiou.com/p/104850.html</a></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-08 00:54:09" itemprop="dateCreated datePublished" datetime="2019-07-08T00:54:09+08:00">2019-07-08</time>
                </span>
                <span id="/6801.html" class="post-meta-item leancloud_visitors" data-flag-title="知识付费的发展趋势" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>2.5k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>2 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6799.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> 技术杂谈 <i class="label-arrow"></i>
                  </a>
                  <a href="/6799.html" class="post-title-link" itemprop="url">未来，你可能会主动点开这种广告</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>首先，这篇文章不是一个广告。 但你可能已经要被这个时代无处不在的洗脑广告逼疯了。 大街小巷贴满的纸皮广告、商场和电梯里循环播放的电子屏广告、电脑上避不开关不掉停不了的网页广告…… 某招聘软件和某旅拍公司的循环怒吼式视频让人生理性地记住了它，但心理上可能想把相关人员关电梯里让它们把这条广告看一万遍。 <img src="https://img2.tuicool.com/YVBRNze.png!web" alt=""> ▲ 图片来自：「伯爵旅拍」广告截图 广告的形式不断在变化，从广播、平面、视频，到 H5、直播，部分广告会以新颖的形式和优质的内容取胜，但人们看广告一直都是被动接受的过程。 上周蜘蛛侠上映，索尼在 Snapchat 上 <a href="https://mobilemarketingmagazine.com/sony-snapchatspider-man-far-from-home-ar-campaign" target="_blank" rel="noopener">推出了 AR 活动宣传</a> ，为了贴合「英雄远征」的主题，索尼让蜘蛛侠「出现」在世界各地的地标处，比如纽约的熨斗大厦、伦敦的白金汉宫、巴黎的艾菲尔铁塔，只要你将自拍镜头对准建筑，就可以看到蜘蛛侠在屏幕上摆动。 <img src="https://img2.tuicool.com/QZNRjaI.jpg!web" alt=""> ▲图片来自：mobilemarketingmagazine 它让人们愿意看蜘蛛侠的广告，同时也让广告成了一种主动参与的过程。 接下来要讲的，就是未来这一种我们会主动点开的——VR 广告。</p>
                  <h3 id="当广告变成了一种「真实」的体验"><a href="#当广告变成了一种「真实」的体验" class="headerlink" title="当广告变成了一种「真实」的体验"></a>当广告变成了一种「真实」的体验</h3>
                  <p>我们看现在的广告，有一种潜意识默认的原则，就是注意力只给它们 3 秒钟。 但现在的 VR 广告，是给你无限的时间，让你自己去体验。 AR 火起来，还是被 2015 年 Snapchat 的搞怪 AR 滤镜，以及 2016 年的神奇宝贝 GO 带动，这也改变了市场营销对 AR 的看法，让 AR 开始在广告界生根发芽。 <img src="https://img1.tuicool.com/naMv6nE.png!web" alt=""> 宜家是首批尝试购买 AR 技术并以此宣传的公司之一。 在 <a href="https://sensortower.com/ios/US/inter-ikea-systems-b-v/app/ikea-place/1279244498/" target="_blank" rel="noopener">IKEA Place app 中</a> ，人们可以看到新款家具实际摆放在家里的样子，以判断它们是不是有足够空间布局放置、颜色风格是否和周围环境搭调，这可以帮宜家提高客户满意度以及降低产品退回率。 <img src="https://img1.tuicool.com/J3iYnqr.gif" alt=""> 随后苹果发布了 ARkit 技术，Google、Facebook、亚马逊等大公司也在 2017 年和 2018 年期间发布了自己 AR 软件开发套件，以支持日后 AR 技术的发展。 Facebook 去年就为广告商提供了 <a href="https://techcrunch.com/2018/07/10/facebook-ar-ads/" target="_blank" rel="noopener">展示产品的新方式</a> ，让人们在新闻流中看到品牌广告时，能够通过前置摄像头对比自己戴上太阳镜等配饰、甚至穿各种衣服的样子。现在 Facebook 也一直在通过各个平台包括 Instagram 扩大购物范围和增值产品。 <img src="https://img1.tuicool.com/VNRRBvZ.png!web" alt=""> 除了家居服饰，美容时尚等行业也开始尝试利用 AR 营销，主要是为了弥补线上和线下的差距。 两周前，人们已经能在 YouTube 上一边看美妆博主的化妆教程，一边在分屏之下跟着博主一起涂口红试色，这个名为 <a href="https://techcrunch.com/2019/06/18/youtubes-new-ar-beauty-try-on-lets-viewers-virtually-try-on-makeup-while-watching-video-reviews/" target="_blank" rel="noopener">AR Beauty Try-On 的功能</a> 由 Google 推出，M・A・C 是第一个跟上这项 AR 广告的品牌。测试结果发现，人们愿意花费超过 80 秒的时间去体验虚拟唇膏色调。 <img src="https://img1.tuicool.com/NbAJRrm.gif" alt=""> 甚至当你逛淘宝店，在前几周 <a href="https://www.chinanews.com/it/2019/06-25/8874866.shtml" target="_blank" rel="noopener">天猫旗舰店升级的</a> 2.0 版本，能直接让你像逛线下实体店一样逛线上 3D 商店、参观漫游，每家店铺展示内容也因人而异。 而且，你还可以实地进入 AR 广告之中。 在汽车行业，宝马、保时捷、丰田等汽车品牌都成为了 AR 广告的追随者。 丰田上个月给 2020 年卡罗拉 <a href="https://mobile-ar.reality.news/news/toyota-rolls-out-web-based-ar-advertising-campaign-via-8th-wall-0198696/" target="_blank" rel="noopener">推出了 AR 移动广告</a> ，人们可以 360 度全方位观看车内视图和功能，以及固定在一个地方的 3D 数字模型，甚至还可以在车内行走。这能让购买者更简单地了解车辆的技术特性，也有助于在购买车辆前对其预先认证。 <img src="https://img2.tuicool.com/Ivu2ume.gif" alt=""> 这种自然而直观的方式，不仅让人们和品牌建立起更真实的关系，与以往广告最大的不同就是，人们还能随时参与、持续互动，并响应内容。</p>
                  <h3 id="我们可以拿回看广告的主动权"><a href="#我们可以拿回看广告的主动权" class="headerlink" title="我们可以拿回看广告的主动权"></a>我们可以拿回看广告的主动权</h3>
                  <p>现在，我们自己可以掌控广告。 目前我们 <a href="https://www.clickz.com/ar-in-marketing-iab-playbook/241746/" target="_blank" rel="noopener">能看到的 AR 广告</a> ，主要包括被赞助的拍照滤镜镜头、应用软件和游戏内的 AR 广告单元，信息流内的 AR 广告单元等。 之前是广告「邀请」我们看，没有任何协商。但现在我们「侵入」广告，去满足自己的好奇心。广告变成了一个工具，我们通过它与我们喜欢的品牌获得联系。 这种形式现在也让 AR 广告成为当今市场上破坏性最小的广告单元之一。 <img src="https://img1.tuicool.com/z63EnqU.png!web" alt=""> ▲ 图片来自：unsplash 另外，AR 广告已经不再是单独的一个内容，而是进入我们的真实生活，或者进入其它我们沉浸的内容之中——广告本身就是商品的展示位，不会再有其它如美化特效、夸张视觉等因素插入和干扰。 而且这些 AR 广告最初打开的阀门，就掌握在消费者手上。 因为 AR 广告现在都需要申请对其相机／设备的访问，在非相机优先的应用中，也需要申请特定权限，因此你完全可以选择你需要的内容，并与之开始进一步互动。 <img src="https://img2.tuicool.com/aiUJVnA.jpg!web" alt=""> ▲ 图片来自： <a href="https://www.cisin.com/coffee-break/technology/augmented-reality-ar-technology-sets-free-an-enormous-company-development-prospects.html" target="_blank" rel="noopener">Cyber Infrastructure</a> 可以说，AR 就是有史以来最强大的故事叙述媒体。它的互动带来的「响应式特色」，就是它最大的优势。 Unity 技术的 AR / VR 广告创新 <a href="https://appdevelopermagazine.com/why-ar-advertising-is-hard/" target="_blank" rel="noopener">负责人 Tony Parisi 说</a> ： 响应性 AR 广告是一种更加友好，不那么令人生畏的方法，这些广告能够提高客户的选择率，同时通过控制权来提高用户的舒适度。 尽管 AR 广告现在还不足以吸引大多数用户，人们似乎更觉得这只是消费者的一种自我意淫。 但实际上幸运的是，现在它背后有一批最大的科技公司在支持和推动，这就让更多设计师有机会去创造更好的 AR 广告环境，更多品牌有空间去施放更大的营销活动。 Google、苹果、Facebook 这样的大平台仍然主导着 AR 领域及其营销的发展。这些科技公司还和零售商 <a href="https://www.mobilemarketer.com/news/more-than-140-retailers-tech-companies-team-up-to-improve-arvr/553412/" target="_blank" rel="noopener">组成了一个小组</a> ，为用于增强现实购物的 3D 图像创建一套通用标准。 <img src="https://img2.tuicool.com/juaaIzr.gif" alt=""> ▲ Google 推出名为 Swirl 的沉浸式显示，消费者可以 360 度查看、旋转、放大、缩小商品 在这个背景之下，市场研究公司 <a href="https://www.seaberyat.com/augmented-reality-stats/" target="_blank" rel="noopener">Markets and Markets 的数据</a> 预计，到明年 AR 将拥有 10 亿用户，到 2022 年将达到 22 亿美元的广告支出。 AR 广告进入我们的生活将会更加快速，而我们掌握的主动权也会越来越多，正如以色列最大的互联网公司 IronSource 首席设计官 <a href="http://www.adotas.com/2017/11/__trashed-115/" target="_blank" rel="noopener">Dan Greenberg</a> <a href="http://www.adotas.com/2017/11/__trashed-115/" target="_blank" rel="noopener">所认为的</a> ： 我们不断研究如何在广告中为用户提供尽可能多的选择的方法。因为给用户提供更多控制权，才是创建真正个性化移动体验的关键。</p>
                  <h3 id="AR-会是下一个广告形态"><a href="#AR-会是下一个广告形态" class="headerlink" title="AR 会是下一个广告形态"></a>AR 会是下一个广告形态</h3>
                  <p>数字广告总体上正朝着更丰富、更身临其境、更多选择的方向发展，消费者正在被置于互动体验的中心。 <a href="https://www.mobilemarketer.com/news/study-agencies-face-cost-tech-hurdles-with-ar-advertising/557067/" target="_blank" rel="noopener">根据 Gartner 的估计</a> ，随着 5G 高速移动服务的推出，明年它将推动 AR 购物增长到 1 亿消费者。 <img src="https://img2.tuicool.com/bI77bau.png!web" alt=""> ▲ 图片来自：unsplash 因为在高速运行的网络下，开发者不仅能够创造更高质量的 3D 可视化技术，消费者也能获得更多形式的沉浸式体验，品牌也能将其购物平台更多扩展到商店和传统网站之外的移动设备上，而且广告也不会再只限于移动设备的小屏幕。 还可以预见的是，5G 网络之下数据信息密度也会迅速加大，除了文本，还有图像、视频，人们接受的购物信息无疑更广、更深、更繁杂，而 AR 能够降低广告的感知成本，让人们消费数字内容将更简便和直观。 <img src="https://img0.tuicool.com/2QRNfqF.png!web" alt=""> ▲ 图片来自：unsplash 但现在 AR 广告还未普及的主要问题，就在于它的成本和技术。 比起其他广告形式来说，其实它的投入成本并不低。Digiday 指出 <a href="https://www.skyword.com/contentstandard/marketing/snapchat-ar-ads-are-now-more-affordable-making-it-a-go-to-ar-destination/" target="_blank" rel="noopener">单一的广告体验</a> 可能需要花费 5,000 到 30,000 美元甚至更多才能正常开发，投资回报率也很难衡量。 而近阶段虽然 AR 技术发展很快，但 5G 手机还刚推出第一批，5G 网络也并未稳定普及，这也是影响 AR 广告现在扩大规模的关键因素——没有与之匹配的设备。另外，比起其它开发多年的其它形式的广告，AR 广告迭代和优化也还不太灵活，并非广告商们信手就可捏来。 <img src="https://img1.tuicool.com/Y3Uz2uV.png!web" alt=""> ▲ 图片来自：unsplash AR 热潮刚刚开始真正起飞，我们现在看到的只是关于 AR 能带来什么以及它能做些什么的一瞥。不过苹果 2017 年推出 ARKit 时，首席执行官蒂姆・库克在 <a href="https://www.mobilemarketer.com/news/mobile-disruptor-of-the-year-augmented-reality/510150/" target="_blank" rel="noopener">财报电话会议上就指出</a> AR 将成为「主流」： 虽然我们现在还只触及冰山一角，但它将永久改变我们使用技术的方式。 而这个未来正在靠近。Digi-Capital <a href="https://www.mobilemarketer.com/news/mobile-disruptor-of-the-year-augmented-reality/510150/" target="_blank" rel="noopener">预计在未来五年内</a> AR 广告的市场价值将达到 830 亿美元。而且下一代移动设备，还会为我们带来更多新的可能性。 折叠手机的柔性屏幕将能让 AR 广告拥有更多形式；内置投影仪的手机，能让 AR 广告用更大的位置呈现，当广告变成了一种看电影的方式，这对广告商也意味着有更多的空间来宣传。 <img src="https://img2.tuicool.com/NzMNNrq.jpg!web" alt=""> ▲ 图片来自： <a href="https://www.satzuma.com/product-page/3d-hologram-visualiser" target="_blank" rel="noopener">satzuma</a> 但无论何种新的形式，接下来，当我们沉迷于自己所选的 AR 广告体验中无法自拔，会不会更加停不下来「买买买」呢？ 我开始觉得这是一篇关于 AR 的沉浸式广告了。 原文：<a href="https://www.ifanr.com/1230440?utm_source=tuicool&amp;utm_medium=referral" target="_blank" rel="noopener">https://www.ifanr.com/1230440</a></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-08 00:52:27" itemprop="dateCreated datePublished" datetime="2019-07-08T00:52:27+08:00">2019-07-08</time>
                </span>
                <span id="/6799.html" class="post-meta-item leancloud_visitors" data-flag-title="未来，你可能会主动点开这种广告" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>3.3k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>3 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6797.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> 技术杂谈 <i class="label-arrow"></i>
                  </a>
                  <a href="/6797.html" class="post-title-link" itemprop="url">阿里新开发了一款神器，零基础也能轻松做出酷炫动效！</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>阿里作为中国互联网企业的标杆，不仅一直在为广大人民群众提供快捷便利的服务。也不断引领行业，在各个专业领域，给专业人员提供了优质的工具与服务。对设计师而言，无论是每年一度的 UCAN大会，还是 Kitchen、语雀、Ant Design 等设计开发工具，都给广大设计师带来很大的便利。 而最近，阿里又推出了一款基于 Lottie 的动效设计平台 ── 犸良。能够快速生成设计师想要的动态效果，并交付给开发，极大地提高了设计效率和设计还原度。</p>
                  <h4 id="犸良是什么？"><a href="#犸良是什么？" class="headerlink" title="犸良是什么？"></a>犸良是什么？</h4>
                  <p>它是一个积累了很多通用动效素材的平台，让不会动效的同学们也能基于动效库和素材库快速生成一个通用动效创意，你只需要简单地编辑图片、颜色或者文字即可。同时平台集成了以 Lottie 为代表的动效技术，让曾经令人苦恼的安装包大小和性能问题一并解决。 <img src="https://img2.tuicool.com/uQFZNzN.gif" alt=""> △ 官网： <a href="https://design.alipay.com/emotion" target="_blank" rel="noopener">https://design.alipay.com/emotion</a></p>
                  <h4 id="犸良的应用场景"><a href="#犸良的应用场景" class="headerlink" title="犸良的应用场景"></a>犸良的应用场景</h4>
                  <p>犸良支持全平台 iOS、Android、H5、小程序。无论是营销展位、活动页面、空状态还是产品icon。犸良编辑器对接投放平台，一站式完成动效创意制作和投放。 丰富的动效素材能够满足多种场景需要。</p>
                  <h4 id="犸良怎么用？"><a href="#犸良怎么用？" class="headerlink" title="犸良怎么用？"></a>犸良怎么用？</h4>
                  <h4 id="方式一：基于模版直接制作（适用于设计师进行动态banner制作）"><a href="#方式一：基于模版直接制作（适用于设计师进行动态banner制作）" class="headerlink" title="方式一：基于模版直接制作（适用于设计师进行动态banner制作）"></a>方式一：基于模版直接制作（适用于设计师进行动态banner制作）</h4>
                  <ol>
                    <li>选择模版</li>
                    <li>从动画仓库选择动画进行当前动画的替换</li>
                    <li>通过替换图片或修改颜色来自定义动画</li>
                    <li>自定义模板文字内容</li>
                    <li>选择模板背景图片</li>
                    <li>完成编辑选择是否带背景（banner模版默认带背景）</li>
                    <li>导出成功下载 json</li>
                  </ol>
                  <h4 id="方式二：自定义画布制作（适用于设计师进行模板覆盖不到的动效应用场景）"><a href="#方式二：自定义画布制作（适用于设计师进行模板覆盖不到的动效应用场景）" class="headerlink" title="方式二：自定义画布制作（适用于设计师进行模板覆盖不到的动效应用场景）"></a>方式二：自定义画布制作（适用于设计师进行模板覆盖不到的动效应用场景）</h4>
                  <p>使用犸良一分钟做出来的效果，大家感受下： <img src="https://img0.tuicool.com/ZZFjIn3.gif" alt=""> <img src="https://img1.tuicool.com/M7Bjuuy.gif" alt=""></p>
                  <h4 id="体验感受"><a href="#体验感受" class="headerlink" title="体验感受"></a>体验感受</h4>
                  <p>综合体验了一下犸良，相对于鹿班人工智能随意撸图。猛良其实是一款基于人工的动效模板网站。所有的动态效果都是设计师提前做好的。基于 Lottie 的特性，将可编辑的属性进行模块化。 说到这里有些设计师可能有疑问了，Lottie 究竟是什么？在优设已经有相关文章介绍的比较全面了，这里就不赘述了，阅读链接： <a href="http://www.uisdc.com/tag/lottie/" target="_blank" rel="noopener">http://www.uisdc.com/tag/lottie/</a> 。 简言之 Lottie 就是一个快速将 AE 中设计的动效快速调出，并且开发可以直接调用的动效库。 Lottie 的原理是将矢量图形和动态效果通过代码实现，复杂的图形导出图片资源。最终生成 json 文件+图片资源。犸良所做的就是，将 json 里的颜色属性提取出来，用户可以从外部修改。同样只需要替换图片资源，就可以让用户自己设计的图形根据 json 写好的运动方式来展示。如果你把一只移动的猪替换成猫了，那么你看到的效果就是一只移动的猫。 而它的文字编辑功能，我觉得就比较神奇了。因为 Lottie 貌似不能从外部修改字体，应该是阿里自己写的。如果有知道的同学也欢迎解惑。因为我们在实际工作中经常遇到这样的问题，就是如果动效包含文本了，需要适配国际化，就没法从外部修改语言。如果能解决这个问题，将会减少很多麻烦。 从现有的素材来看，其主要应用场景还是电商和运营方面。相信未来会增加更多的素材和应用场景。希望以后能够开放出让设计师可以将自己做的动效分享出来供大家使用，或者提供优质付费内容也是不错的选择。 就个人的工作经验来看，Lottie 还是有丰富的应用场景的。比如可以做动态图标、动态闪屏页、表情贴纸、直播礼物、空白页、动态banner…… 其实 Lottie 官方社区也有很多的动效资源，都是来自全世界的设计师上传的，大家可以去下载参考，如果要商用，可能需要联系作者。 <img src="https://img1.tuicool.com/zuuqe2R.gif" alt=""> △ 附上链接： <a href="https://lottiefiles.com/popular" target="_blank" rel="noopener">https://lottiefiles.com/popular</a> 本文资料，很多都是来自语雀的分享。再次感谢相关人员的辛勤付出。大家可以在此处查看犸良的完整介绍： <a href="https://www.yuque.com/dapolloali/news/ui43vg" target="_blank" rel="noopener">https://www.yuque.com/dapolloali/news/ui43vg</a> 。 再次附上官网： <a href="https://design.alipay.com/emotion" target="_blank" rel="noopener">https://design.alipay.com/emotion</a> 因为犸良是基于 Lottie 的工具，所以有相关基础的设计师很容易上手。还不了解的同学也可以在各大设计网站搜索到。 欢迎关注作者的微信公众号：「懿凡设计」 本文原文：<a href="https://www.uisdc.com/alibaba-emotion" target="_blank" rel="noopener">https://www.uisdc.com/alibaba-emotion</a></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-08 00:43:19" itemprop="dateCreated datePublished" datetime="2019-07-08T00:43:19+08:00">2019-07-08</time>
                </span>
                <span id="/6797.html" class="post-meta-item leancloud_visitors" data-flag-title="阿里新开发了一款神器，零基础也能轻松做出酷炫动效！" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>1.7k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>2 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6702.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> 个人随笔 <i class="label-arrow"></i>
                  </a>
                  <a href="/6702.html" class="post-title-link" itemprop="url">对崔庆才的个人采访记录</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>前几天，公众号「人工智能爱好者社区」的负责人书豪对我进行了一次采访，问了我一些问题，其中有些问题确实有深度，我在此一一进行了作答，这里将其记录下来。</p>
                  <h2 id="当前觉得自己最不可替代性或最有优势的劳动价值在哪里？"><a href="#当前觉得自己最不可替代性或最有优势的劳动价值在哪里？" class="headerlink" title="当前觉得自己最不可替代性或最有优势的劳动价值在哪里？"></a>当前觉得自己最不可替代性或最有优势的劳动价值在哪里？</h2>
                  <p>有一种不断追求更优的心吧。 作为一名程序员，就拿写代码来说，比如做一个项目，我会把自己看作是负责人，以负责人的心态来做这件事情，自己主动有这份心去把这件事做好，而不是别人分配给我我做完就应付完事了。 在做项目过程中我会一直想着自己怎么实现是更优化的，如架构怎样设计扩展性更好，算法模块怎样效率更高，用户怎样使用会更方便。另外我一般会从多个角度来优化自己的项目，比如站在产品角度思考这么实现的用户体验，站在程序员角度思考更好的技术方案，站在设计角度思考布局交互。 此外就是感觉自己有点强迫症，如果某件事做不完，会一直在心里挂念着，直到把这件事完成。</p>
                  <h2 id="觉得自己最牛逼的能力是什么？"><a href="#觉得自己最牛逼的能力是什么？" class="headerlink" title="觉得自己最牛逼的能力是什么？"></a>觉得自己最牛逼的能力是什么？</h2>
                  <p>不能说是牛逼的能力，因为我觉得我并没有什么牛逼的能力。我只能说有一些习惯或特性会对完成一件事情或达成一个目标更有帮助。 比如在学习的时候我会有做记录的习惯，学习的时候我会把我理解到的东西记录下来，然后学习完毕之后会把记录和理解的知识点归纳和总结一下，在这个过程中确实会需要对整个知识点进一步的思考，确实是非常有帮助的。 另外一个或许就是自控能力吧，我不敢说自己的自控能力是多么强，因为我自己确实也有时候是什么都不想做的。不过当自己一旦开始做一件事的时候，我会控制自己不去做的无关的事情，比如编写一个程序我可能一天到晚都会专注于这件事情，在没有完成的时候总觉得心里有件事没完成，于是乎就会继续干下去，可能熬个通宵也会想把它做完 ，这可以总结为一旦开始了就不容易停下。但自己也有个缺点，就是有时候就是觉得一件事麻烦不想开始做，不容易开始。所以我也在改正这个缺点。如果做事情的时候能够容易开始、持续专注，那就更好了。 另外就是上文提到的，自己有个追求完美的心。比如做一件事的时候会想追求完美和极致，一旦某处觉得不是很切合自己的想法，我会想办法把它变得更好一点。但我也不知道这样是不是好的，因为可能有些时候会把自己搞得比较累，但是事情的结果总归是相对好一点的。</p>
                  <h2 id="工作以来，经历的困难是什么？如何面对的，是如何爬出这段艰难的处境的？"><a href="#工作以来，经历的困难是什么？如何面对的，是如何爬出这段艰难的处境的？" class="headerlink" title="工作以来，经历的困难是什么？如何面对的，是如何爬出这段艰难的处境的？"></a>工作以来，经历的困难是什么？如何面对的，是如何爬出这段艰难的处境的？</h2>
                  <p>我正式工作只有三个月，不过工作之前已经在微软实习了一年多的时间了。 我自己有个特点就是不想给别人添麻烦，即使自己的事情压得太多，承担太多，也不想给别人添什么麻烦。所以很多时候我会把一些事情扛在自己身上，也不容易去拒绝一些事情，所以之前有时候我会同时并行着非常多的事情。 比如某个同学让我帮忙做点事情，我会答应并把它加到自己的待做清单里面；然后导师、领导又同时安排了一些事情；然后同事又让我帮忙做点事情。好，我基本都会答应然后去处理，即使真的自己压的喘不过气来了，也不想去再找另外的人帮忙或者推脱掉。直到有一天，我累得生病了，发了好几天烧也不舒服，很多事情也没有完成。 有一次我去找我当时在微软的宋老师交流的时候，她告诉我了一些方法。其实我这种做法是不对的，一些事情没有必要一定会去答应和接受，并把所有的责任都扛在自己身上，一些事情要学会拒绝，并去主动和别人确定好事情的重要程度、紧急程度，以及你真的有没有必要去帮某些人做某些事情。时间是自己的，要学会合理安排自己的时间，如果遇到新来的事情，要跟对方说清楚，比如我正在忙，你的事情我可能得晚几天才能帮你。另外遇到突发的情况或者实在让自己为难的事情，就多去跟对方商量，确定下事情的紧急程度、重要程度，然后再去合理分配自己的时间。后来我尝试做出了一些改变，一些事情不再无条件强加到自己身上，并会主动与对方沟通情况以把握好处理每件事情的时机，我的心态和生活规律也就慢慢地调整过来了。</p>
                  <h2 id="你经历过的至暗时刻大致是什么样的，为什么说它是你的至暗时刻，主观能力和客观环境当时是什么样的？"><a href="#你经历过的至暗时刻大致是什么样的，为什么说它是你的至暗时刻，主观能力和客观环境当时是什么样的？" class="headerlink" title="你经历过的至暗时刻大致是什么样的，为什么说它是你的至暗时刻，主观能力和客观环境当时是什么样的？"></a>你经历过的至暗时刻大致是什么样的，为什么说它是你的至暗时刻，主观能力和客观环境当时是什么样的？</h2>
                  <p>每个人都会经历过很多挫折，并且会在经历挫折的时候由于当时的心智和世界观觉得那个挫折就是无法抵抗的“至暗时刻”，我大学时候被劈腿，高考失利，初中脑炎，甚至小学的时候摔断胳膊和食物中毒昏迷一周，都觉得那是在我能力范围内无法解决的事情了，但实际上我并没有把它们称为是”至暗时刻“。 我在遇到挫折的时候，我会想，两年之后或者十年之后，这个事情对我有没有影响。很多情况下是没有的，所以，它仅仅是我成长历程上的一个小坎，迈过去就好了。很多人在经历所谓“至暗时刻”的时候，总以为自己处在这个黑暗圆圈的最中央最无助最绝望的地方，但至暗时刻其实是一个圆环，你只需要从直径 8 走到直径 10 的地方就可以看到明天和未来，如果这么想的话，大多数的至暗时刻都不值一提了。</p>
                  <h2 id="你印象中对你影响最深刻的人是哪一位，他给了你什么样的启发和力量？"><a href="#你印象中对你影响最深刻的人是哪一位，他给了你什么样的启发和力量？" class="headerlink" title="你印象中对你影响最深刻的人是哪一位，他给了你什么样的启发和力量？"></a>你印象中对你影响最深刻的人是哪一位，他给了你什么样的启发和力量？</h2>
                  <p>我小时候其实成长条件还算不错的，父母几乎从来没有打过我骂过我，都是以鼓励的心态来培养我的，其实父母是对我影响最深刻的人，没有他们就没有我的现在。 不过除了父母以外就再提一位让我印象比较深刻的老师吧。之所以印象深刻可能就是因为他是惟一一位打过我的老师，因为人总是能对和平常状态反差强烈的事情印象最为深刻。 当时是高中上晚自习的时候，我没有好好写作业，而是拿着我的学习机在看游戏的图片，正好被老师抓到了，他把我拽出教室，狠狠地打了我的胸口两拳，当时他问过我一句话：“你知道什么是‘慎独’吗？”，我当时沉默了，他接着说：“‘慎独’就是谨慎独行，就是在别人没有监督你的时候，自己能够管住自己做正确的事情。” 从那以后，我便把这两个字一直记在心里，知道什么时候应该去做什么，当我向偷懒或者被某些事情诱惑的时候，我会尽力地控制自己。到现在为止，我个人觉得自己的自控力或专注力还算是比较强了。 真的非常感谢老师当年给我上的这一课。</p>
                  <h2 id="技术成长最快的一段时间当时面对的是什么样的环境下，为什么说这段时间成长环境最快？"><a href="#技术成长最快的一段时间当时面对的是什么样的环境下，为什么说这段时间成长环境最快？" class="headerlink" title="技术成长最快的一段时间当时面对的是什么样的环境下，为什么说这段时间成长环境最快？"></a>技术成长最快的一段时间当时面对的是什么样的环境下，为什么说这段时间成长环境最快？</h2>
                  <p>我认为不论是做什么，成长最快的时间基本上就是刚刚接触这个领域的时刻，成长的速度会慢慢地放缓。所以技术上来说，成长最快的时间可以说是我大学刚刚接触技术领域各个方向的时候了。 大学那会儿初步了解了一些编程理念之后，我加入了一个实验室，那会儿实验室分了很多方向，可以说是学校中技术方向最为全面的实验室了，包括前端、后台、安卓、iOS、美工、游戏等等，那会儿还有几个非常厉害的学长带着我们，带我们去学习和探索很多技术上的东西，那会儿就接触了 Git、Web 开发、移动开发、游戏开发，参加了非常多的比赛，做了一系列的外包，搭建自己的博客，分享自己的技术。总的来说学到了很多，真的特别感谢当时带我的几位学长，有了平台，有人带飞，的确是可以飞速成长的。</p>
                  <h2 id="未来-3-到-5-年的打算是什么？打算如何突破这个打算，量化来说，困难程度有多大？需要的运气成分有多大？"><a href="#未来-3-到-5-年的打算是什么？打算如何突破这个打算，量化来说，困难程度有多大？需要的运气成分有多大？" class="headerlink" title="未来 3 到 5 年的打算是什么？打算如何突破这个打算，量化来说，困难程度有多大？需要的运气成分有多大？"></a>未来 3 到 5 年的打算是什么？打算如何突破这个打算，量化来说，困难程度有多大？需要的运气成分有多大？</h2>
                  <p>现在我自己来看，我还是一个初出茅庐的小喽啰，不论是技术、金钱、人脉都还是比较薄弱的。我其中一个目标便是能够在生活上立稳脚跟，不为生活上的各种条件所困，娶到我的女朋友并一起过上富足快乐的日子。再长远的目标就是实现各种自由，如财务自由、时间自由。 要达成这个目标的确还是比较有挑战性的，我还需要非常的多的努力。当前初步的打算一定是努力工作，自己的主业上能够稳扎稳打，自己的主要工作必须要做好，从技术等各个层面上提升自己。另外其他的时间我会想办法累积自己的一些软实力，比如提升自己的知名度、积累自己的人脉等等。这个困难没法量化，也几乎不会靠什么运气成分，需要靠自己稳扎稳打地来一步步地做。</p>
                  <h2 id="如果有天你失业了，会如何面对这个处境，当前的危机在哪里？"><a href="#如果有天你失业了，会如何面对这个处境，当前的危机在哪里？" class="headerlink" title="如果有天你失业了，会如何面对这个处境，当前的危机在哪里？"></a>如果有天你失业了，会如何面对这个处境，当前的危机在哪里？</h2>
                  <p>其实这个对于来说倒不怕，我本是技术出身，某个时代总是会有当前时代所最流行的技术的。如果但从技术方面看话，我可以不断地去学习和输出，即使是我四十多岁了，我一样可以做一个学习者，那会儿可能拼精力比不过，但那会儿构思能力、总结能力还是不会差的，至于知识的变现，方式就太多了。 但肯定不能仅靠这个来生存，多少还是有一定的压力的。其他的副业一定也是需要发展的，比如理财、投资、写作、服务等多种形式都可以成为经济的来源。所以我个人建议不要把所有的精力都完全压在主业上，推荐适当发展一下副业。</p>
                  <h2 id="你拿到过最好的工作机会的这段经历自己是如何准备这个过程的？"><a href="#你拿到过最好的工作机会的这段经历自己是如何准备这个过程的？" class="headerlink" title="你拿到过最好的工作机会的这段经历自己是如何准备这个过程的？"></a>你拿到过最好的工作机会的这段经历自己是如何准备这个过程的？</h2>
                  <p>我拿到的最好的工作机会就是我当前选择的微软了，这边工作氛围我非常喜欢，各种待遇都算不错。 由于我当时是在微软实习，所以最后是通过转正的途径来参加面试的。转正面试我非常非常重视，在工作的同时我提前准备了面试可能问的各个方面的问题。这边面试首先要求基本的算法能力是过关的，这是一个硬性要求，如果这都写不出来可能会被直接 Pass 的，所以当时一直在刷题、看数据结构、算法题等等，确保一些 LeetCode 上面简单和中等难度的题目都能比较稳地做出来。由于我面试的岗位对算法、工程能力都比较看重，所以那会儿还准备了很多机器学习算法、熟悉了相关的机器学习模型，比如逻辑回归、SVM、BP 算法的具体推导过程。对于工程方面这个可能真的要看平时的积累了，由于我之前做过很多的工程类项目，包括网络爬虫、前端开发、后端开发、框架搭建等等，所以简历上的项目还算比较说得过去，面试的时候聊一聊其中的一些理念和架构就好了。 当时面试真的特别重视，所以准备面试的过程也是非常焦虑，整个准备时间一个多月，所以当时可以算是焦虑了一个多月吧，当时也同时在忙公司和学校的项目，可以说压力是非常大的，不过好在取得了不错的结果，得知面试结果的一刻也是舒了一口气，感谢自己曾经的努力。</p>
                  <h2 id="你觉得自己拥有什么样的缺点，这些缺点，是什么原因和经历造成的，未来的你，会选择什么样的行业和岗位进行跟进突击？"><a href="#你觉得自己拥有什么样的缺点，这些缺点，是什么原因和经历造成的，未来的你，会选择什么样的行业和岗位进行跟进突击？" class="headerlink" title="你觉得自己拥有什么样的缺点，这些缺点，是什么原因和经历造成的，未来的你，会选择什么样的行业和岗位进行跟进突击？"></a>你觉得自己拥有什么样的缺点，这些缺点，是什么原因和经历造成的，未来的你，会选择什么样的行业和岗位进行跟进突击？</h2>
                  <p>我觉得我个人有个缺点，就是挺多事情不是特别主动。这确实就是自己的性格导致的，我挺多事情并不想去麻烦别人，也不想去占用别人的时间，很多事情愿意靠自己的力量去探索去达成。 在我的成长历程上，挺多事情好像都是别人找到我，比如加好友、比如合作项目，基本都是别人找到我，然后我觉得 OK，就去答应然后做了，目前看来这并不是一件坏事，也对我的生活没有产生一些负面影响。 但是这样其实我无形中失去了一些可能我能力上能触及的更好的机会，比如结交更多朋友，合作更好的项目。所以，以后我也会去求变，去主动结实更优秀的人，去主动找寻更好的机会。 至于行业和岗位的话，我对我目前的行业和岗位是非常满意的，我在目前我的计划里面也会从事当前的行业和岗位的。</p>
                  <h2 id="对于投资风险你有什么样的理解？你如何打理自己的资金？理财策略是什么？"><a href="#对于投资风险你有什么样的理解？你如何打理自己的资金？理财策略是什么？" class="headerlink" title="对于投资风险你有什么样的理解？你如何打理自己的资金？理财策略是什么？"></a>对于投资风险你有什么样的理解？你如何打理自己的资金？理财策略是什么？</h2>
                  <p>投资会伴随着风险，这是毋庸置疑的，对于投资的回报，虽然说存在非常多的不可控因素，但风险的大小是可以通过一定的观察、计算、经验来的出来的。不同的投资和操作，其对结果的影响是不同的。 投资我通常和理财挂钩，因为广义上的大额的投资对我来说还是不现实的，目前的我是会在我现有的资金基础上进行适当的理财的。 我会把我的资产划分为四个部分。第一部分是危急时刻可以救命的，这部分是完全不能动的，就存起来，当自己危机的情况下可以取出来保证自己正常的生存的，量的大小的话就按照能满足自己正常生活一年的水平来存就好。第二部分是作为自己的固有资产来存的，比如为了自己将来买房子、车子来做准备用的，这部分基本上就是只增不减的，平时也不会动用这部分的资产。第三部分是满足自己日常花销和生活的，这部分基本上我就放在了支付宝和微信里面，平时买点东西或者正常开支来使用。最后一部分就是用来做理财的，由于我是一个相对比较保守型的，这部分占比不算多，我会拿这些钱去购买一些比较高风险高收益的基金等，观察某个情形出售，然后短期捞利收手再去出手下一个，当然赚的还不多，也有些情况下由于判断失误就亏了不少。不过这部分的盈亏我自己都是可以接受的，不会影响我的正常生活。 总的来说，我个人建议适当划分一下自己的资产及分配，然后在自己可承受的范围内适当去理财和投资。当然如果钱多的话当我什么都没有说哈。</p>
                  <h2 id="庆才是个有感情经历的人，我想问你，对于男人寻找结婚伴侣或者找女朋友，你觉得最需要对方什么样的特质，有哪些变量是必须要的，有哪些变量是可以剔除的？"><a href="#庆才是个有感情经历的人，我想问你，对于男人寻找结婚伴侣或者找女朋友，你觉得最需要对方什么样的特质，有哪些变量是必须要的，有哪些变量是可以剔除的？" class="headerlink" title="庆才是个有感情经历的人，我想问你，对于男人寻找结婚伴侣或者找女朋友，你觉得最需要对方什么样的特质，有哪些变量是必须要的，有哪些变量是可以剔除的？"></a>庆才是个有感情经历的人，我想问你，对于男人寻找结婚伴侣或者找女朋友，你觉得最需要对方什么样的特质，有哪些变量是必须要的，有哪些变量是可以剔除的？</h2>
                  <p>对于伴侣的选择，我个人觉得性格三观合得来、脾气好是非常重要的。因为性格和脾气基本上就是与生俱来的东西，这个是非常难改的。如果两个人性格上合不来，比如很多事情上处事态度不同、三观不一致，每次产生冲突的时候是会感觉非常累的，另外如果脾气上两个人都控制不好，事情可能会进一步变得艰难。 另外相貌其实也挺重要的，我不得不承认自己确实也是一个看脸的人，而且看得还蛮重的。这不仅仅是为了自己觉得开心和舒服，也有一部分会为后代考虑吧。 所以有一句名言说的好，相貌决定了你愿不愿意去了解一个人内心，而内心决定了你会不会一票否决这个人的样貌。</p>
                  <h2 id="你的第一桶金是如何获得的，是自己，还是团队一起，这件事的主观能力方面当时的难度有多大，客观上，你创造了哪些条件达成它？"><a href="#你的第一桶金是如何获得的，是自己，还是团队一起，这件事的主观能力方面当时的难度有多大，客观上，你创造了哪些条件达成它？" class="headerlink" title="你的第一桶金是如何获得的，是自己，还是团队一起，这件事的主观能力方面当时的难度有多大，客观上，你创造了哪些条件达成它？"></a>你的第一桶金是如何获得的，是自己，还是团队一起，这件事的主观能力方面当时的难度有多大，客观上，你创造了哪些条件达成它？</h2>
                  <p>第一桶金如果不论大小的话，那就是大学时候和实验室的同学一起做外包赚到的，几千到上万不等，做过不少项目，具体记不太清了。 完成这些项目，大部分是技术上的突破，当时我更多是以技术开发的身份参与到其中的，由于当时对相关的技术栈还算比较了解，所以当时并没有觉得实现难度上有多大，不过当时也非常感谢实验室和学校能提供对接的平台，让我们的技术能力得以更好地发挥。</p>
                  <h2 id="当下，你最担心什么，最害怕什么，为何会有这个恐惧，你会如何破局？"><a href="#当下，你最担心什么，最害怕什么，为何会有这个恐惧，你会如何破局？" class="headerlink" title="当下，你最担心什么，最害怕什么，为何会有这个恐惧，你会如何破局？"></a>当下，你最担心什么，最害怕什么，为何会有这个恐惧，你会如何破局？</h2>
                  <p>思来想去没想到有什么担心和害怕的，我目前对自己的生活还是比较喜欢的，另外我也对将来的生活充满信心。不过我现在并没有什么心思会考虑将来会出现什么让我担心和害怕的事，因为可能这些事情的出现都是不可预料到的吧。 所以，与其去担心这些事情，还不如着眼当下，去努力拼搏。</p>
                  <h2 id="你觉得自己在商业能力上与技术能力上，哪个更有优势，为什么？"><a href="#你觉得自己在商业能力上与技术能力上，哪个更有优势，为什么？" class="headerlink" title="你觉得自己在商业能力上与技术能力上，哪个更有优势，为什么？"></a>你觉得自己在商业能力上与技术能力上，哪个更有优势，为什么？</h2>
                  <p>现在肯定还是技术能力上更有优势，因为自己目前的定位就是技术为主的工程师，我目前赖以生存的能力就是自己的技术。商业能力上，我并没有什么经验，人脉也不算广，当然这肯定也是我想扩展的一个方向之一。</p>
                  <h2 id="平时工作中会用到哪些算法，在算法方面的能力你是如何突破的？"><a href="#平时工作中会用到哪些算法，在算法方面的能力你是如何突破的？" class="headerlink" title="平时工作中会用到哪些算法，在算法方面的能力你是如何突破的？"></a>平时工作中会用到哪些算法，在算法方面的能力你是如何突破的？</h2>
                  <p>现在工作做的事情涉及的方向比较多，包括前端、后台、机器学习、图像处理、自然语言处理等技术，爬虫是我自己的附加职责了。 算法方面，自然语言处理方向涉及较多。由于我更加专注于产品和落地，所以会更偏向于使用一些实用和更有效率的算法，保证上线和实际使用的真实效果。另外对于一些前沿的模型，可能和大多数搞科研的同学一样，我也会去搜索当前前沿的论文来看，找找开源代码，然后尝试对接实现，不断迭代调整自己的算法。</p>
                  <h2 id="你做过最成功最有成就感的一件事是什么？"><a href="#你做过最成功最有成就感的一件事是什么？" class="headerlink" title="你做过最成功最有成就感的一件事是什么？"></a>你做过最成功最有成就感的一件事是什么？</h2>
                  <p>应该就是写出来了一本爬虫相关的书籍，并帮助了很多人。 我研究爬虫时间不短了，当时图灵的王编辑找我约稿的时候我还是非常激动和欣喜的，当时正好也想借机把自己学过的爬虫知识系统地做一个梳理，写书的过程很辛苦，不过后来顺利出版了，现在销量也已经远超我的预期。 现在还有不少读者加我，说看了我的书收获很大，有的读者还是看了我的书顺利转行爬虫并找到了工作，听到这些消息，真的非常非常有成就感，希望我的书对读者有帮助。</p>
                  <h2 id="你做股票投资亏损最严重的经历是什么样的，亏损的原因可否总结？"><a href="#你做股票投资亏损最严重的经历是什么样的，亏损的原因可否总结？" class="headerlink" title="你做股票投资亏损最严重的经历是什么样的，亏损的原因可否总结？"></a>你做股票投资亏损最严重的经历是什么样的，亏损的原因可否总结？</h2>
                  <p>我没做过股票，只做过一些高风险高收益的基金投资，亏损倒不严重，都在我自己的可控范围之内。 亏损的一些原因可以稍微归结下，一个就是自己对市场的把握程度不够，有时候听别人说好，或者看着短期的势头不错就直接出手买了。另外就是自己平时工作忙，无暇分配那么多的时间来关注市场行情的变化，结果导致下次看到的时候，已经跌到没法看了。</p>
                  <h2 id="你对于人性是什么样的看法，可以结合自己的工作经历或者投资经历谈谈。"><a href="#你对于人性是什么样的看法，可以结合自己的工作经历或者投资经历谈谈。" class="headerlink" title="你对于人性是什么样的看法，可以结合自己的工作经历或者投资经历谈谈。"></a>你对于人性是什么样的看法，可以结合自己的工作经历或者投资经历谈谈。</h2>
                  <p>人性这个东西非常复杂，这个看法就太宽泛了。 体会最深的一点就是，不要试图去改变一个人，很多都是天生的，难以更改的。改变可能仅仅发生在某些重大变化和突发情况下，或者从小的教育。所以，对于我们日常生活和工作来看，一个人三观不合，或者性格不合，还是不要去修正了，这太难了。所以，我的选择是对于这样的人，少去接触、少去交流、少去合作。不过好在我现在几乎没有碰到多少难以交流和合作的人，但一旦有，我还是会持有那样的态度的。毕竟让一个自己不认可的人来浪费自己的时间是非常亏的。</p>
                  <h2 id="人生中有无数的风险，为规避人生的种种风险，你做了什么？"><a href="#人生中有无数的风险，为规避人生的种种风险，你做了什么？" class="headerlink" title="人生中有无数的风险，为规避人生的种种风险，你做了什么？"></a>人生中有无数的风险，为规避人生的种种风险，你做了什么？</h2>
                  <p>风险我主要就考虑两个方面了，一个是健康风险，一个是资金风险。 健康风险，对于我这一行，可能听得最多的就是程序员过劳猝死了。其实我也挺害怕的，有时候我也会熬夜，有时候工作强度也很大，有时候也会久坐不动。所以我会控制自己的时间，比如坐的时间长了就起来活动一下，注意波保护自己的颈椎腰椎等等，另外定期体检，看看自己身体有什么不良状况及时整治。佛祖保佑，我是不会猝死的好不好。 资金风险，这个是上文所提到的，主业技术方向，需要保持一个终身学习能力，靠自己的技术能力通过多种方式变现。另外也得注意发展一下副业，比如投资、理财、写作等等方向，并且要培养一个可以累积和增长的能力，比如写作，其水平就是慢慢累积的，而且是持续可增长的，这种就比较稳了。总之建议大家多个方向都去尝试一下，不要把鸡蛋放在同一个篮子里。</p>
                  <h2 id="你是如何追到这么优秀的女朋友的？"><a href="#你是如何追到这么优秀的女朋友的？" class="headerlink" title="你是如何追到这么优秀的女朋友的？"></a>你是如何追到这么优秀的女朋友的？</h2>
                  <p>关于这个问题我是比较困惑的啊，为什么一定是我追的不能是她追的我或者是互相喜欢呢？当然，事实上，确实算得上我“追”她，不过此追非彼追。我觉得很多人之所以觉得追女孩子困难，是因为大家追的不是这个女孩子跟自己脾气性格符合，而是追的漂亮，这样一来自然会容易觉得聊天总陷入死路，如果换个想法，只是寻找和自己脾气性格符合能够开心的在一起的（当然最好还能漂亮一点），在一起就非常顺其自然啦。所以我女朋友经常说我们之间不存在我追她或者她追我，因为两个人接触之后就觉得应该在一起啦~ 最后欢迎大家关注「崔庆才」的个人公众号「进击的Coder」 <img src="https://qiniu.cuiqingcai.com/2019-06-02-163318.jpg" alt=""></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-04 19:05:12" itemprop="dateCreated datePublished" datetime="2019-07-04T19:05:12+08:00">2019-07-04</time>
                </span>
                <span id="/6702.html" class="post-meta-item leancloud_visitors" data-flag-title="对崔庆才的个人采访记录" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>7.2k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>7 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6763.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> Python <i class="label-arrow"></i>
                  </a>
                  <a href="/6763.html" class="post-title-link" itemprop="url">微信公众号文章爬取之：微信自动化</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>本文转载自：<a href="https://www.chenwenguan.com/wechat-browse-automation/" target="_blank" rel="noopener">陈文管的博客-微信公众号文章爬取之：微信自动化</a> 本文内容详细介绍微信公众号历史文章自动化浏览脚本的实现，配合服务端对公众号文章数据爬取来实现微信公众号文章数据的采集。服务端爬取实现见：<a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener">微信公众号文章爬取之：服务端数据采集</a>。 背景：在团队的学习方面需要每周收集开发方面的博客文章，汇总输出每周的技术周报。周报小组成员收集的文章大多数是来自微信公众号，公众号的内容相对网页博客内容质量还是比较高的。既然数据的来源是确定的，收集汇总的流程是确定的，那么就把这个流程自动化，把人工成本降低到0。</p>
                  <h3 id="一、方案选取"><a href="#一、方案选取" class="headerlink" title="一、方案选取"></a>一、方案选取</h3>
                  <h4 id="1、数据源选取"><a href="#1、数据源选取" class="headerlink" title="1、数据源选取"></a>1、数据源选取</h4>
                  <p>主要是爬取的数据来源选取，网上资料看的较多是爬取搜狗微信的内容，但是第三方平台（包括新榜、清博等 ）的公众号文章数据更新做不到实时，而且数据也不全，还要和各种反爬措施斗智斗勇，浪费时间精力的事情划不来。最直接的方式当然是直接爬取微信公众号历史文章里面的内容。 在前期预研主要参考的资料是知乎专栏：<a href="https://zhuanlan.zhihu.com/c_65943221" target="_blank" rel="noopener">微信公众号内容的批量采集与应用</a> 。 上面的方案是借助阿里巴巴开源的<a href="http://anyproxy.io/cn/" target="_blank" rel="noopener">AnyProxy</a>工具，AnyProxy作为一个中间人在微信客户端和服务器之间的交互过程中做数据截获和转发。获取到公众号文章的实际链接地址之后转发到自己的服务器进行保存，整个数据采集的自动化程度较大取决于微信客户端的自动化浏览实现。</p>
                  <h4 id="2、自动化方案选取"><a href="#2、自动化方案选取" class="headerlink" title="2、自动化方案选取"></a>2、自动化方案选取</h4>
                  <p>如果是比较简单的安卓应用自动化操作的实现，一般直接使用AccessibilityService就行，UIAutomator也是基于AccessibilityService来实现的，但是AccessibilityService不支持WebView的操作，因为微信公众号历史文章页面是用WebView来加载的，要实现自动化必须同时支持安卓原生和WebView两个上下文环境的操作。 经过现有的几个自动化方案实现对比，最便利又具备极佳扩展性的方案就是使用<a href="http://appium.io/" target="_blank" rel="noopener">Appium</a>。</p>
                  <ul>
                    <li>Appium是开源的移动端自动化测试框架；</li>
                    <li>支持Native App、Hybird App、Web App；</li>
                    <li>支持Android、iOS、Firefox OS；</li>
                    <li>跨平台，可以在Mac，Windows以及Linux系统上；</li>
                    <li>用Appium自动化测试不需要重新编译App；</li>
                    <li>支持Java、python、ruby、C#、Objective C、PHP等主流语言;</li>
                  </ul>
                  <p>更多资料参考：<a href="https://www.cnblogs.com/zeo-to-one/p/6618611.html" target="_blank" rel="noopener">Android自动化测试框架</a></p>
                  <p><a href="https://www.chenwenguan.com/wechat-browse-automation/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/06/wechat-gzh-spider-system.png" alt="公众号文章爬取系统架构图"></a></p>
                  <p>公众号文章爬取系统架构图</p>
                  <h3 id="二、Appium安装配置（Mac）"><a href="#二、Appium安装配置（Mac）" class="headerlink" title="二、Appium安装配置（Mac）"></a>二、Appium安装配置（Mac）</h3>
                  <p>Appium程序的安装，我这边不是使用brew命令安装的方式，直接从<a href="https://bitbucket.org/appium/appium.app/downloads/" target="_blank" rel="noopener">BitBucket</a>下载Appium安装包，也可以从<a href="https://github.com/appium/appium-desktop/releases" target="_blank" rel="noopener">Github</a>上下载。这边使用BitBucket 1.5.3版本。 Appium1.5.0之后的版本，需要在终端安装doctor，在终端输入命令：npm install -g appium-doctor，安装完毕之后，在终端输入命令：appium-doctor，查看所需的各个配置是否都已经安装配置完毕。下面是我这边在终端输出得到的结果：</p>
                  <figure class="highlight sql">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">info AppiumDoctor Appium Doctor v.1.4.3</span><br><span class="line">info AppiumDoctor <span class="comment">### Diagnostic starting ###</span></span><br><span class="line">info AppiumDoctor  ✔ The Node.js binary was found at: /Users/chenwenguan/.nvm/versions/node/v8.9.3/bin/node</span><br><span class="line">info AppiumDoctor  ✔ Node version is 8.9.3</span><br><span class="line">info AppiumDoctor  ✔ Xcode is installed at: /Library/Developer/CommandLineTools</span><br><span class="line">info AppiumDoctor  ✔ Xcode Command Line Tools are installed.</span><br><span class="line">info AppiumDoctor  ✔ DevToolsSecurity is enabled.</span><br><span class="line">info AppiumDoctor  ✔ The Authorization DB is <span class="keyword">set</span> up properly.</span><br><span class="line">WARN AppiumDoctor  ✖ Carthage was <span class="keyword">NOT</span> <span class="keyword">found</span>!</span><br><span class="line">info AppiumDoctor  ✔ HOME <span class="keyword">is</span> <span class="keyword">set</span> <span class="keyword">to</span>: /<span class="keyword">Users</span>/chenwenguan</span><br><span class="line">WARN AppiumDoctor  ✖ ANDROID_HOME <span class="keyword">is</span> <span class="keyword">NOT</span> <span class="keyword">set</span>!</span><br><span class="line">WARN AppiumDoctor  ✖ JAVA_HOME <span class="keyword">is</span> <span class="keyword">NOT</span> <span class="keyword">set</span>!</span><br><span class="line">WARN AppiumDoctor  ✖ adb could <span class="keyword">not</span> be <span class="keyword">found</span> because ANDROID_HOME <span class="keyword">is</span> <span class="keyword">NOT</span> <span class="keyword">set</span>!</span><br><span class="line">WARN AppiumDoctor  ✖ android could <span class="keyword">not</span> be <span class="keyword">found</span> because ANDROID_HOME <span class="keyword">is</span> <span class="keyword">NOT</span> <span class="keyword">set</span>!</span><br><span class="line">WARN AppiumDoctor  ✖ emulator could <span class="keyword">not</span> be <span class="keyword">found</span> because ANDROID_HOME <span class="keyword">is</span> <span class="keyword">NOT</span> <span class="keyword">set</span>!</span><br><span class="line">WARN AppiumDoctor  ✖ <span class="keyword">Bin</span> <span class="keyword">directory</span> <span class="keyword">for</span> $JAVA_HOME <span class="keyword">is</span> <span class="keyword">not</span> <span class="keyword">set</span></span><br><span class="line">info AppiumDoctor <span class="comment">### Diagnostic completed, 7 fixes needed. ###</span></span><br><span class="line">info AppiumDoctor </span><br><span class="line">info AppiumDoctor <span class="comment">### Manual Fixes Needed ###</span></span><br><span class="line">info AppiumDoctor The configuration cannot be automatically <span class="keyword">fixed</span>, please <span class="keyword">do</span> the <span class="keyword">following</span> <span class="keyword">first</span>:</span><br><span class="line">WARN AppiumDoctor - Please <span class="keyword">install</span> Carthage. Visit https://github.com/Carthage/Carthage<span class="comment">#installing-carthage for more information.</span></span><br><span class="line">WARN AppiumDoctor - Manually configure ANDROID_HOME.</span><br><span class="line">WARN AppiumDoctor - Manually configure JAVA_HOME.</span><br><span class="line">WARN AppiumDoctor - Manually configure ANDROID_HOME <span class="keyword">and</span> run appium-doctor again.</span><br><span class="line">WARN AppiumDoctor - <span class="keyword">Add</span> <span class="string">'$JAVA_HOME/bin'</span> <span class="keyword">to</span> your <span class="keyword">PATH</span> environment</span><br><span class="line">info AppiumDoctor <span class="comment">###</span></span><br><span class="line">info AppiumDoctor </span><br><span class="line">info AppiumDoctor Bye! Run appium-doctor again <span class="keyword">when</span> <span class="keyword">all</span> <span class="keyword">manual</span> fixes have been applied!</span><br><span class="line">info AppiumDoctor</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>上面打叉的都是没配置好的，在终端输入命令安装Carthage ：brew install carthage</p>
                  <p>输入命令查看JDK安装路径：/usr/libexec/java_home -V</p>
                  <figure class="highlight groovy">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="number">1.8</span><span class="number">.0</span>_60, <span class="string">x86_64:</span> <span class="string">"Java SE 8"</span> <span class="regexp">/Library/</span>Java<span class="regexp">/JavaVirtualMachines/</span>jdk1<span class="number">.8</span><span class="number">.0</span>_60.jdk<span class="regexp">/Contents/</span>Home</span><br><span class="line"><span class="regexp">/Library/</span>Java<span class="regexp">/JavaVirtualMachines/</span>jdk1<span class="number">.8</span><span class="number">.0</span>_60.jdk<span class="regexp">/Contents/</span>Home</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>需要把上面的路径配置到环境变量中，ANDROID_HOME就是Android SDK的安装路径。</p>
                  <p>输入命令打开配置文件： open ~/.bash_profile，在文件中添加如下内容：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="builtin-name">export</span> <span class="attribute">JAVA_HOME</span>=/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home  </span><br><span class="line"><span class="builtin-name">export</span> <span class="attribute">PATH</span>=<span class="variable">$JAVA_HOME</span>/bin:$PATH  </span><br><span class="line"><span class="builtin-name">export</span> <span class="attribute">ANDROID_HOME</span>=/Users/chenwenguan/Library/Android/sdk</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>输入命令让配置立即生效：source ~/.bash_profile</p>
                  <p>更多安装配置资料可参考：<a href="http://www.cnblogs.com/zoeya/p/6022481.html" target="_blank" rel="noopener">Mac上搭建Appium环境过程以及遇到的问题</a></p>
                  <h3 id="TIPS"><a href="#TIPS" class="headerlink" title="TIPS"></a>TIPS</h3>
                  <p>在首次使用Appium时可能会出现一个错误：</p>
                  <figure class="highlight sas">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">Could <span class="meta">not</span> detect Mac OS <span class="meta">X</span> Version <span class="meta">from</span> sw_vers <span class="meta">output</span>: <span class="string">'10.13.2</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>在终端输入命令：</p>
                  <figure class="highlight gradle">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">grep</span> -rl <span class="string">"Could not detect Mac OS X Version from sw_vers output"</span> <span class="regexp">/Applications/</span>Appium.app<span class="regexp">/</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>得到如下结果：</p>
                  <figure class="highlight crystal">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">/Applications/Appium.app/<span class="regexp">/Contents/</span>Resources/node_modules/appium-support/<span class="class"><span class="keyword">lib</span>/<span class="title">system</span>.<span class="title">js</span></span></span><br><span class="line">/Applications/Appium.app/<span class="regexp">/Contents/</span>Resources/node_modules/appium-support/build/<span class="class"><span class="keyword">lib</span>/<span class="title">system</span>.<span class="title">js</span></span></span><br><span class="line">/Applications/Appium.app/<span class="regexp">/Contents/</span>Resources/node_modules/appium/node_modules/appium-support/<span class="class"><span class="keyword">lib</span>/<span class="title">system</span>.<span class="title">js</span></span></span><br><span class="line">/Applications/Appium.app/<span class="regexp">/Contents/</span>Resources/node_modules/appium/node_modules/appium-support/build/<span class="class"><span class="keyword">lib</span>/<span class="title">system</span>.<span class="title">js</span></span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>打开上面四个路径下的文件，添加当前的Appium版本参数，具体内容可参考：<a href="https://blog.csdn.net/smallHorseDaDa/article/details/72865685?locationNum=12&amp;fps=1" target="_blank" rel="noopener">在Mac OS 10.12 上安装配置appium</a></p>
                  <h3 id="三、具体代码实现"><a href="#三、具体代码实现" class="headerlink" title="三、具体代码实现"></a>三、具体代码实现</h3>
                  <p>预研资料主要参考这篇博文：<a href="https://testerhome.com/topics/6954" target="_blank" rel="noopener">Appium 微信 webview 的自动化技术</a> 自动化实现的原理就是通过ID或者模糊匹配找到相应的控件，之后对这个控件做点击、滑动等操作。如果要对微信WebView做自动化，必须能够获取到WebView里面的对象，如果是Android原生的控件可以通过AndroidStudio里面的Android Device Monitor来查看控件的id、类名等各种属性。</p>
                  <h4 id="1、Android原生控件属性参数值的获取"><a href="#1、Android原生控件属性参数值的获取" class="headerlink" title="1、Android原生控件属性参数值的获取"></a>1、Android原生控件属性参数值的获取</h4>
                  <p>在AndroidStudio打开Monitor工具：Tools-&gt;Android-&gt;Android Device Monitor 按照下图的步骤查看控件的ID等属性，后续在代码实现中会用到。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-browse-automation/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/06/android-device-monitor-1024x583.jpg" alt="Android Device Monitor"></a></p>
                  <p>Android Device Monitor</p>
                  <h4 id="2、WebView属性参数值的获取"><a href="#2、WebView属性参数值的获取" class="headerlink" title="2、WebView属性参数值的获取"></a>2、WebView属性参数值的获取</h4>
                  <p>如果是在安卓真机上，需要打开WebView的调试模式才能读取到WebView的各个属性，在微信里面可以在任意聊天窗口输入debugx5.qq.com，这是微信x5内核调试页面，在信息模块中勾选打开TBS内核Inspector调试功能。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-browse-automation/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/06/wechat-x5-debug-169x300.jpg" alt="微信X5内核调试页面"></a></p>
                  <p>微信X5内核调试页面</p>
                  <p>之后还要在真机上<strong>安装Chrome浏览器</strong>，如果是在虚拟机上无需做此操作。 接下来在Chrome浏览器中输入：<a href="chrome://inspect/">chrome://inspect</a> ，我这边使用的是虚拟机，真机上也一样，进入到公众号历史文章页面，这边就会显示相应可检视的WebView页面，点击inspect，进入到Developer Tools页面。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-browse-automation/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/06/chrome-inspect-1024x585.jpg" alt="chrome inspect页面"></a></p>
                  <p>chrome inspect页面</p>
                  <p>如果进入到Developer Tools页面显示一片空白，是因为chrome inspect需要加载 <a href="https://chrome-devtools-frontend.appspot.com" target="_blank" rel="noopener">https://chrome-devtools-frontend.appspot.com</a> 上的资源，所以需要翻墙，把appstop.com 加入翻墙代理白名单，或者直接全局应用翻墙VPN，具体可参考：<a href="https://github.com/HuarenYu/note/issues/1" target="_blank" rel="noopener">使用chrome remote debug时打开inspect时出现一片空白</a> 下面是美团技术团队历史文章列表的详细结构信息，具体的文章列表项在weui-panel-&gt;weui-panel__bd appmsg_history_container-&gt;js_profile_history_container-&gt;weui_msg_card_list路径下。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-browse-automation/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/06/chrome-inspect-developer-tools-1024x635.jpg" alt="Chrome inspect查看WebView详细内容"></a></p>
                  <p>Chrome inspect查看WebView详细内容</p>
                  <p>继续展开节点查看文章详细结构信息，这边可以看到每篇文章的ID都是以“WXAPPMSG100″开头的，类名都是“weui_media_box”开头，一开始的实现是通过模糊匹配ID来查找历史文章列表项数据，但在测试过程中出现来一个异常，后来发现，如果是纯文本类型的文章，也就是只有一段话的文章，它是没有ID的，所以不能通过ID来模糊匹配。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-browse-automation/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/06/wechat-webview-detail-1024x801.jpg" alt="公众号历史文章列表项详细结构"></a></p>
                  <p>公众号历史文章列表项详细结构</p>
                  <p>之后就把现有的四种公众号文章类型都找来出来，找它们的共性，虽然ID不一定有，但是class类型值一定有，四种类型值如下，这样就可以通过class类型值来匹配查找数据了。</p>
                  <figure class="highlight ada">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"> * weui_media_box appmsg js_appmsg : 文章</span><br><span class="line"> * weui_media_box text js_appmsg : 文本</span><br><span class="line"> * weui_media_box img js_appmsg : 图片</span><br><span class="line">* weui_media_box appmsg audio_msg_primary js_appmsg playing : 语音</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h4 id="3、具体代码实现"><a href="#3、具体代码实现" class="headerlink" title="3、具体代码实现"></a>3、具体代码实现</h4>
                  <p>整体自动化是按照如下顺序：通讯录页面-&gt;点击公众号进入公众号列表页面-&gt;公众号列表项选择一个点击-&gt;公众号页面-&gt;公众号消息页面-&gt;点击“全部消息”进入公众号历史文章页面-&gt;根据设置的时间类型（一周之内、一个月之内、一年之内或者全部）逐个点击历史文章列表项，完毕之后返回公众号列表页面，继续下一个公众号浏览的操作；</p>
                  <h5 id="1）初始化"><a href="#1）初始化" class="headerlink" title="1）初始化"></a>1）初始化</h5>
                  <figure class="highlight reasonml">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="keyword">private</span> static AndroidDriver get<span class="constructor">Driver()</span> throws MalformedURLException &#123;</span><br><span class="line">        DesiredCapabilities capability = <span class="keyword">new</span> <span class="constructor">DesiredCapabilities()</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"platformName"</span>, <span class="string">"emulator-5554"</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"platformVersion"</span>, <span class="string">"4.4.4"</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"deviceName"</span>, <span class="string">"MuMu"</span>)</span>;</span><br><span class="line">         <span class="comment">/**</span></span><br><span class="line"><span class="comment">          * 真机上platformName使用"Android"</span></span><br><span class="line"><span class="comment">          */</span></span><br><span class="line">         <span class="comment">/*</span></span><br><span class="line"><span class="comment">         capability.setCapability("platformName", "Android");</span></span><br><span class="line"><span class="comment">         capability.setCapability("platformVersion", "6.0");</span></span><br><span class="line"><span class="comment">         capability.setCapability("deviceName", "FRD-AL00");</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"unicodeKeyboard"</span>,<span class="string">"True"</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"resetKeyboard"</span>,<span class="string">"True"</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"app"</span>, <span class="string">""</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"appPackage"</span>, <span class="string">"com.tencent.mm"</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"appActivity"</span>, <span class="string">".ui.LauncherUI"</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"fastReset"</span>, <span class="params">false</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"fullReset"</span>, <span class="params">false</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"noReset"</span>, <span class="params">true</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"newCommandTimeout"</span>, 2000)</span>;</span><br><span class="line">         <span class="comment">/**</span></span><br><span class="line"><span class="comment">          * 必须加这句，否则webView和native来回切换会有问题</span></span><br><span class="line"><span class="comment">          */</span></span><br><span class="line">         capability.set<span class="constructor">Capability(<span class="string">"recreateChromeDriverSessions"</span>, <span class="params">true</span>)</span>;</span><br><span class="line">         <span class="comment">/**</span></span><br><span class="line"><span class="comment">          * 关键是加上这段</span></span><br><span class="line"><span class="comment">          */</span></span><br><span class="line">         ChromeOptions options = <span class="keyword">new</span> <span class="constructor">ChromeOptions()</span>;</span><br><span class="line">         options.set<span class="constructor">ExperimentalOption(<span class="string">"androidProcess"</span>, <span class="string">"com.tencent.mm:tools"</span>)</span>;</span><br><span class="line">         capability.set<span class="constructor">Capability(ChromeOptions.CAPABILITY, <span class="params">options</span>)</span>;</span><br><span class="line">         String url = <span class="string">"http://127.0.0.1:4723/wd/hub"</span>;</span><br><span class="line">         mDriver = <span class="keyword">new</span> AndroidDriver&lt;&gt;(<span class="keyword">new</span> <span class="constructor">URL(<span class="params">url</span>)</span>, capability);</span><br><span class="line">         return mDriver;</span><br><span class="line">     &#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>如果是虚拟机则platformName使用具体的虚拟机名称，如果是真机使用“Android”，platformVersion和deviceName可以使用工程安装APK之后查看详细信息，对应的参数就是显示的系统版本和设备名称。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-browse-automation/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/06/wechat-deviceInfo-169x300.jpg" alt="设备信息"></a></p>
                  <p>设备信息</p>
                  <p>URL参数是在Appium里面设置的，确保”<a href="http://127.0.0.1:4723/wd/hub”字符串中的服务器地址和端口与Appium设置一致。" target="_blank" rel="noopener">http://127.0.0.1:4723/wd/hub”字符串中的服务器地址和端口与Appium设置一致。</a></p>
                  <p><a href="https://www.chenwenguan.com/wechat-browse-automation/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/06/appium-setting-1024x884.jpg" alt="Appium URL参数设置"></a></p>
                  <p>Appium URL参数设置</p>
                  <h5 id="2）列表滑动和元素获取"><a href="#2）列表滑动和元素获取" class="headerlink" title="2）列表滑动和元素获取"></a>2）列表滑动和元素获取</h5>
                  <p>不管是WebView还是Android原生ListView的滑动都需要在Android原生上下文环境下操作driver.context(“NATIVE_APP”); 滑动操作都可以通过如下代码实现，通过滑动前后的PageSource对比可以知道列表是否已经滑动到底部。</p>
                  <figure class="highlight arduino">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">     <span class="comment">/**</span></span><br><span class="line"><span class="comment">      * 滑动列表加载下一页数据</span></span><br><span class="line"><span class="comment">      *</span></span><br><span class="line"><span class="comment">      * @param driver</span></span><br><span class="line"><span class="comment">      * @return</span></span><br><span class="line"><span class="comment">      * @throws InterruptedException</span></span><br><span class="line"><span class="comment">      */</span></span><br><span class="line">     <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isScrollToBottom</span><span class="params">(AndroidDriver driver)</span> throws InterruptedException </span>&#123;</span><br><span class="line">         <span class="keyword">int</span> <span class="built_in">width</span> = driver.manage().window().getSize().<span class="built_in">width</span>;</span><br><span class="line">         <span class="keyword">int</span> <span class="built_in">height</span> = driver.manage().window().getSize().<span class="built_in">height</span>;</span><br><span class="line">         <span class="keyword">String</span> beforeswipe = driver.getPageSource();</span><br><span class="line">         driver.swipe(<span class="built_in">width</span> / <span class="number">2</span>, <span class="built_in">height</span> * <span class="number">3</span> / <span class="number">4</span>, <span class="built_in">width</span> / <span class="number">2</span>, <span class="built_in">height</span> / <span class="number">4</span>, <span class="number">1000</span>);</span><br><span class="line">         <span class="comment">/**</span></span><br><span class="line"><span class="comment">          * 设置8s超时，网络较差情况下时间过短加载不出内容</span></span><br><span class="line"><span class="comment">          */</span></span><br><span class="line">         mDriver.manage().timeouts().implicitlyWait(<span class="number">8000</span>, TimeUnit.MILLISECONDS);</span><br><span class="line">         <span class="keyword">String</span> afterswipe = driver.getPageSource();</span><br><span class="line">         <span class="comment">/**</span></span><br><span class="line"><span class="comment">          * 已经到底部</span></span><br><span class="line"><span class="comment">          */</span></span><br><span class="line">         <span class="keyword">if</span> (beforeswipe.equals(afterswipe)) &#123;</span><br><span class="line">             <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">         &#125;</span><br><span class="line">         <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">     &#125;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p><strong>TIPS：</strong> <strong>如果是Android原生的ListView读取到的数据是在屏幕上显示的数据，超过屏幕的数据是获取不到的，如果是WebView的列表获取的数据是所有已加载的数据，不管是否在屏幕显示范围内。</strong> 获取公众号列表数据逻辑代码如下，”com.tencent.mm:id/a0y”是具体的公众号名称TextView的ID。</p>
                  <figure class="highlight abnf">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">List&lt;WebElement&gt; elementList = mDriver.findElementsById(<span class="string">"com.tencent.mm:id/a0y"</span>)<span class="comment">;</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>获取历史文章列表数据逻辑代码如下，div是节点，上面说到公众号四种类型的文章都是以’weui_media_box’类名开头的，通过模糊匹配class类名以’weui_media_box’开始的元素来过滤出所有的公众号文章列表项。</p>
                  <figure class="highlight lasso">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="built_in">List</span>&lt;WebElement&gt; msgHistory = driver.findElements(<span class="keyword">By</span>.xpath(<span class="string">"//div[starts-with(@class,'weui_media_box')]"</span>));</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h5 id="3）元素定位方式"><a href="#3）元素定位方式" class="headerlink" title="3）元素定位方式"></a>3）元素定位方式</h5>
                  <p>如果一定需要模糊匹配就使用By.xpath()的方式，因为Android APK应用如果有增加或减少了布局字符串资源或者控件，编译之后生成的ID可能会不一样，这边说的ID是指通过Android Device Monitor查看的布局ID，不是实际的布局代码控件id，布局控件id除非命名改动，否则不会变化。所以<strong>不同版本的微信客户端生成的ID很可能会不一样</strong>，如果要批量实现自动化最好使用模糊匹配的方式，但By.xpath()方式查找定位元素是遍历页面的所有元素，会比较耗时，也容易出现异常。 在测试过程中执行</p>
                  <figure class="highlight less">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="selector-tag">driver</span><span class="selector-class">.findElement</span>(By.xpath(<span class="string">"//android.widget.ImageView[@content-desc='返回']"</span>))<span class="selector-class">.click</span>();</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>时候经常出现如下错误，改为</p>
                  <figure class="highlight less">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="selector-tag">driver</span><span class="selector-class">.findElementById</span>(<span class="string">"com.tencent.mm:id/ht"</span>)<span class="selector-class">.click</span>();</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>异常消失，猜测原因就是因为By.xpath()方法查找比较耗时导致。</p>
                  <figure class="highlight stylus">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.WebDriverException</span>: An unknown server-side error occurred while processing the command. (WARNING: The server did not provide any stacktrace information)</span><br><span class="line"> Command duration or timeout: <span class="number">1.41</span> seconds</span><br><span class="line"> Build info: version: <span class="string">'2.44.0'</span>, revision: <span class="string">'76d78cf'</span>, <span class="selector-tag">time</span>: <span class="string">'2014-10-23 20:02:37'</span></span><br><span class="line"> System info: host: <span class="string">'wenguanchen-MacBook-Pro.local'</span>, ip: <span class="string">'30.85.214.6'</span>, os<span class="selector-class">.name</span>: <span class="string">'Mac OS X'</span>, os<span class="selector-class">.arch</span>: <span class="string">'x86_64'</span>, os<span class="selector-class">.version</span>: <span class="string">'10.13.2'</span>, java<span class="selector-class">.version</span>: <span class="string">'1.8.0_112-release'</span></span><br><span class="line"> Driver info: io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.android</span>.AndroidDriver</span><br><span class="line"> Capabilities [&#123;appPackage=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>, noReset=true, dontStopAppOnReset=true, deviceName=emulator-<span class="number">5554</span>, fullReset=false, platform=LINUX, deviceUDID=emulator-<span class="number">5554</span>, desired=&#123;app=, appPackage=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>, recreateChromeDriverSessions=true, noReset=true, dontStopAppOnReset=true, deviceName=MuMu, fullReset=false, appActivity=<span class="selector-class">.ui</span><span class="selector-class">.LauncherUI</span>, platformVersion=<span class="number">4.4</span>.<span class="number">4</span>, automationName=Appium, unicodeKeyboard=true, fastReset=false, chromeOptions=&#123;args=[], extensions=[], androidProcess=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>:tools&#125;, platformName=Android, resetKeyboard=true&#125;, platformVersion=<span class="number">4.4</span>.<span class="number">4</span>, webStorageEnabled=false, automationName=Appium, takesScreenshot=true, javascriptEnabled=true, unicodeKeyboard=true, platformName=Android, resetKeyboard=true, app=, networkConnectionEnabled=true, recreateChromeDriverSessions=true, warnings=&#123;&#125;, databaseEnabled=false, appActivity=<span class="selector-class">.ui</span><span class="selector-class">.LauncherUI</span>, locationContextEnabled=false, fastReset=false, chromeOptions=&#123;args=[], extensions=[], androidProcess=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>:tools&#125;&#125;]</span><br><span class="line"> Session ID: <span class="number">592813</span>d6-<span class="number">7</span>c6e-<span class="number">4</span>a3c-<span class="number">8183</span>-e5f93d1d3bf0</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeConstructorAccessorImpl</span>.newInstance0(Native Method)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeConstructorAccessorImpl</span>.newInstance(NativeConstructorAccessorImpl<span class="selector-class">.java</span>:<span class="number">62</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.DelegatingConstructorAccessorImpl</span>.newInstance(DelegatingConstructorAccessorImpl<span class="selector-class">.java</span>:<span class="number">45</span>)</span><br><span class="line">  at java<span class="selector-class">.lang</span><span class="selector-class">.reflect</span><span class="selector-class">.Constructor</span>.newInstance(Constructor<span class="selector-class">.java</span>:<span class="number">423</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.ErrorHandler</span>.createThrowable(ErrorHandler<span class="selector-class">.java</span>:<span class="number">204</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.ErrorHandler</span>.throwIfResponseFailed(ErrorHandler<span class="selector-class">.java</span>:<span class="number">156</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.RemoteWebDriver</span>.execute(RemoteWebDriver<span class="selector-class">.java</span>:<span class="number">599</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.DefaultGenericMobileDriver</span>.execute(DefaultGenericMobileDriver<span class="selector-class">.java</span>:<span class="number">27</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.AppiumDriver</span>.execute(AppiumDriver<span class="selector-class">.java</span>:<span class="number">1</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.android</span><span class="selector-class">.AndroidDriver</span>.execute(AndroidDriver<span class="selector-class">.java</span>:<span class="number">1</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.RemoteWebDriver</span>.findElement(RemoteWebDriver<span class="selector-class">.java</span>:<span class="number">352</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.RemoteWebDriver</span>.findElementByXPath(RemoteWebDriver<span class="selector-class">.java</span>:<span class="number">449</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.DefaultGenericMobileDriver</span>.findElementByXPath(DefaultGenericMobileDriver<span class="selector-class">.java</span>:<span class="number">99</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.AppiumDriver</span>.findElementByXPath(AppiumDriver<span class="selector-class">.java</span>:<span class="number">1</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.android</span><span class="selector-class">.AndroidDriver</span>.findElementByXPath(AndroidDriver<span class="selector-class">.java</span>:<span class="number">1</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span>.By<span class="variable">$ByXPath</span>.findElement(By<span class="selector-class">.java</span>:<span class="number">357</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.RemoteWebDriver</span>.findElement(RemoteWebDriver<span class="selector-class">.java</span>:<span class="number">344</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.DefaultGenericMobileDriver</span>.findElement(DefaultGenericMobileDriver<span class="selector-class">.java</span>:<span class="number">37</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.AppiumDriver</span>.findElement(AppiumDriver<span class="selector-class">.java</span>:<span class="number">1</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.android</span><span class="selector-class">.AndroidDriver</span>.findElement(AndroidDriver<span class="selector-class">.java</span>:<span class="number">1</span>)</span><br><span class="line">  at com<span class="selector-class">.example</span><span class="selector-class">.AppiumAutoScan</span>.getArticleDetail(AppiumAutoScan<span class="selector-class">.java</span>:<span class="number">335</span>)</span><br><span class="line">  at com<span class="selector-class">.example</span><span class="selector-class">.AppiumAutoScan</span>.launchBrowser(AppiumAutoScan<span class="selector-class">.java</span>:<span class="number">96</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke0(Native Method)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke(NativeMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">62</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.DelegatingMethodAccessorImpl</span>.invoke(DelegatingMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">43</span>)</span><br><span class="line">  at java<span class="selector-class">.lang</span><span class="selector-class">.reflect</span><span class="selector-class">.Method</span>.invoke(Method<span class="selector-class">.java</span>:<span class="number">498</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.model</span>.FrameworkMethod$<span class="number">1</span>.runReflectiveCall(FrameworkMethod<span class="selector-class">.java</span>:<span class="number">45</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.internal</span><span class="selector-class">.runners</span><span class="selector-class">.model</span><span class="selector-class">.ReflectiveCallable</span>.run(ReflectiveCallable<span class="selector-class">.java</span>:<span class="number">15</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.model</span><span class="selector-class">.FrameworkMethod</span>.invokeExplosively(FrameworkMethod<span class="selector-class">.java</span>:<span class="number">42</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.internal</span><span class="selector-class">.runners</span><span class="selector-class">.statements</span><span class="selector-class">.InvokeMethod</span>.evaluate(InvokeMethod<span class="selector-class">.java</span>:<span class="number">20</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.runLeaf(ParentRunner<span class="selector-class">.java</span>:<span class="number">263</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.BlockJUnit4ClassRunner</span>.runChild(BlockJUnit4ClassRunner<span class="selector-class">.java</span>:<span class="number">68</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.BlockJUnit4ClassRunner</span>.runChild(BlockJUnit4ClassRunner<span class="selector-class">.java</span>:<span class="number">47</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span>.ParentRunner$<span class="number">3</span>.run(ParentRunner<span class="selector-class">.java</span>:<span class="number">231</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span>.ParentRunner$<span class="number">1</span>.schedule(ParentRunner<span class="selector-class">.java</span>:<span class="number">60</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.runChildren(ParentRunner<span class="selector-class">.java</span>:<span class="number">229</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.access$<span class="number">000</span>(ParentRunner<span class="selector-class">.java</span>:<span class="number">50</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span>.ParentRunner$<span class="number">2</span>.evaluate(ParentRunner<span class="selector-class">.java</span>:<span class="number">222</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.run(ParentRunner<span class="selector-class">.java</span>:<span class="number">300</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runner</span><span class="selector-class">.JUnitCore</span>.run(JUnitCore<span class="selector-class">.java</span>:<span class="number">157</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.junit4</span><span class="selector-class">.JUnit4IdeaTestRunner</span>.startRunnerWithArgs(JUnit4IdeaTestRunner<span class="selector-class">.java</span>:<span class="number">117</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.junit4</span><span class="selector-class">.JUnit4IdeaTestRunner</span>.startRunnerWithArgs(JUnit4IdeaTestRunner<span class="selector-class">.java</span>:<span class="number">42</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.rt</span><span class="selector-class">.execution</span><span class="selector-class">.junit</span><span class="selector-class">.JUnitStarter</span>.prepareStreamsAndStart(JUnitStarter<span class="selector-class">.java</span>:<span class="number">262</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.rt</span><span class="selector-class">.execution</span><span class="selector-class">.junit</span><span class="selector-class">.JUnitStarter</span>.main(JUnitStarter<span class="selector-class">.java</span>:<span class="number">84</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke0(Native Method)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke(NativeMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">62</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.DelegatingMethodAccessorImpl</span>.invoke(DelegatingMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">43</span>)</span><br><span class="line">  at java<span class="selector-class">.lang</span><span class="selector-class">.reflect</span><span class="selector-class">.Method</span>.invoke(Method<span class="selector-class">.java</span>:<span class="number">498</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.rt</span><span class="selector-class">.execution</span><span class="selector-class">.application</span><span class="selector-class">.AppMain</span>.main(AppMain<span class="selector-class">.java</span>:<span class="number">147</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>如果容易出现如下异常，则是因为页面的内容还未加载完毕，可以通过</p>
                  <figure class="highlight css">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="selector-tag">mDriver</span><span class="selector-class">.manage</span>()<span class="selector-class">.timeouts</span>()<span class="selector-class">.implicitlyWait</span>(8000, <span class="selector-tag">TimeUnit</span><span class="selector-class">.MILLISECONDS</span>);</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>方法设置下超时等待时间，等待页面内容加载完毕，具体超时时间可自己调试看看设置一个合适的值。</p>
                  <figure class="highlight stylus">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.StaleElementReferenceException</span>: stale element reference: element is not attached to the page document</span><br><span class="line">   (Session info: webview=<span class="number">33.0</span>.<span class="number">0.0</span>)</span><br><span class="line">   (Driver info: chromedriver=<span class="number">2.20</span>.<span class="number">353124</span> (<span class="number">035346203162</span>d32c80f1dce587c8154a1efa0c3b),platform=Mac OS X <span class="number">10.13</span>.<span class="number">2</span> x86_64) (WARNING: The server did not provide any stacktrace information)</span><br><span class="line"> Command duration or timeout: <span class="number">2.41</span> seconds</span><br><span class="line"> For documentation on this error, please visit: http:<span class="comment">//seleniumhq.org/exceptions/stale_element_reference.html</span></span><br><span class="line"> Build info: version: <span class="string">'2.44.0'</span>, revision: <span class="string">'76d78cf'</span>, <span class="selector-tag">time</span>: <span class="string">'2014-10-23 20:02:37'</span></span><br><span class="line"> System info: host: <span class="string">'wenguanchen-MacBook-Pro.local'</span>, ip: <span class="string">'30.85.214.81'</span>, os<span class="selector-class">.name</span>: <span class="string">'Mac OS X'</span>, os<span class="selector-class">.arch</span>: <span class="string">'x86_64'</span>, os<span class="selector-class">.version</span>: <span class="string">'10.13.2'</span>, java<span class="selector-class">.version</span>: <span class="string">'1.8.0_112-release'</span></span><br><span class="line"> Driver info: io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.android</span>.AndroidDriver</span><br><span class="line"> Capabilities [&#123;appPackage=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>, noReset=true, dontStopAppOnReset=true, deviceName=emulator-<span class="number">5554</span>, fullReset=false, platform=LINUX, deviceUDID=emulator-<span class="number">5554</span>, desired=&#123;app=, appPackage=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>, recreateChromeDriverSessions=true, noReset=true, dontStopAppOnReset=true, deviceName=MuMu, fullReset=false, appActivity=<span class="selector-class">.ui</span><span class="selector-class">.LauncherUI</span>, platformVersion=<span class="number">4.4</span>.<span class="number">4</span>, automationName=Appium, unicodeKeyboard=true, fastReset=false, chromeOptions=&#123;args=[], extensions=[], androidProcess=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>:tools&#125;, platformName=Android, resetKeyboard=true&#125;, platformVersion=<span class="number">4.4</span>.<span class="number">4</span>, webStorageEnabled=false, automationName=Appium, takesScreenshot=true, javascriptEnabled=true, unicodeKeyboard=true, platformName=Android, resetKeyboard=true, app=, networkConnectionEnabled=true, recreateChromeDriverSessions=true, warnings=&#123;&#125;, databaseEnabled=false, appActivity=<span class="selector-class">.ui</span><span class="selector-class">.LauncherUI</span>, locationContextEnabled=false, fastReset=false, chromeOptions=&#123;args=[], extensions=[], androidProcess=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>:tools&#125;&#125;]</span><br><span class="line"> Session ID: b5e933e1-<span class="number">0</span>ddf-<span class="number">421</span>d-<span class="number">9144</span>-e423a7bb25b1</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeConstructorAccessorImpl</span>.newInstance0(Native Method)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeConstructorAccessorImpl</span>.newInstance(NativeConstructorAccessorImpl<span class="selector-class">.java</span>:<span class="number">62</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.DelegatingConstructorAccessorImpl</span>.newInstance(DelegatingConstructorAccessorImpl<span class="selector-class">.java</span>:<span class="number">45</span>)</span><br><span class="line">  at java<span class="selector-class">.lang</span><span class="selector-class">.reflect</span><span class="selector-class">.Constructor</span>.newInstance(Constructor<span class="selector-class">.java</span>:<span class="number">423</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.ErrorHandler</span>.createThrowable(ErrorHandler<span class="selector-class">.java</span>:<span class="number">204</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.ErrorHandler</span>.throwIfResponseFailed(ErrorHandler<span class="selector-class">.java</span>:<span class="number">156</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.RemoteWebDriver</span>.execute(RemoteWebDriver<span class="selector-class">.java</span>:<span class="number">599</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.DefaultGenericMobileDriver</span>.execute(DefaultGenericMobileDriver<span class="selector-class">.java</span>:<span class="number">27</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.AppiumDriver</span>.execute(AppiumDriver<span class="selector-class">.java</span>:<span class="number">1</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.android</span><span class="selector-class">.AndroidDriver</span>.execute(AndroidDriver<span class="selector-class">.java</span>:<span class="number">1</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.RemoteWebElement</span>.execute(RemoteWebElement<span class="selector-class">.java</span>:<span class="number">268</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.DefaultGenericMobileElement</span>.execute(DefaultGenericMobileElement<span class="selector-class">.java</span>:<span class="number">27</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.MobileElement</span>.execute(MobileElement<span class="selector-class">.java</span>:<span class="number">1</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.android</span><span class="selector-class">.AndroidElement</span>.execute(AndroidElement<span class="selector-class">.java</span>:<span class="number">1</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.RemoteWebElement</span>.getText(RemoteWebElement<span class="selector-class">.java</span>:<span class="number">152</span>)</span><br><span class="line">  at com<span class="selector-class">.example</span><span class="selector-class">.AppiumAutoScan</span>.getArticleDetail(AppiumAutoScan<span class="selector-class">.java</span>:<span class="number">294</span>)</span><br><span class="line">  at com<span class="selector-class">.example</span><span class="selector-class">.AppiumAutoScan</span>.launchBrowser(AppiumAutoScan<span class="selector-class">.java</span>:<span class="number">110</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke0(Native Method)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke(NativeMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">62</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.DelegatingMethodAccessorImpl</span>.invoke(DelegatingMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">43</span>)</span><br><span class="line">  at java<span class="selector-class">.lang</span><span class="selector-class">.reflect</span><span class="selector-class">.Method</span>.invoke(Method<span class="selector-class">.java</span>:<span class="number">498</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.model</span>.FrameworkMethod$<span class="number">1</span>.runReflectiveCall(FrameworkMethod<span class="selector-class">.java</span>:<span class="number">45</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.internal</span><span class="selector-class">.runners</span><span class="selector-class">.model</span><span class="selector-class">.ReflectiveCallable</span>.run(ReflectiveCallable<span class="selector-class">.java</span>:<span class="number">15</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.model</span><span class="selector-class">.FrameworkMethod</span>.invokeExplosively(FrameworkMethod<span class="selector-class">.java</span>:<span class="number">42</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.internal</span><span class="selector-class">.runners</span><span class="selector-class">.statements</span><span class="selector-class">.InvokeMethod</span>.evaluate(InvokeMethod<span class="selector-class">.java</span>:<span class="number">20</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.runLeaf(ParentRunner<span class="selector-class">.java</span>:<span class="number">263</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.BlockJUnit4ClassRunner</span>.runChild(BlockJUnit4ClassRunner<span class="selector-class">.java</span>:<span class="number">68</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.BlockJUnit4ClassRunner</span>.runChild(BlockJUnit4ClassRunner<span class="selector-class">.java</span>:<span class="number">47</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span>.ParentRunner$<span class="number">3</span>.run(ParentRunner<span class="selector-class">.java</span>:<span class="number">231</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span>.ParentRunner$<span class="number">1</span>.schedule(ParentRunner<span class="selector-class">.java</span>:<span class="number">60</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.runChildren(ParentRunner<span class="selector-class">.java</span>:<span class="number">229</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.access$<span class="number">000</span>(ParentRunner<span class="selector-class">.java</span>:<span class="number">50</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span>.ParentRunner$<span class="number">2</span>.evaluate(ParentRunner<span class="selector-class">.java</span>:<span class="number">222</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.run(ParentRunner<span class="selector-class">.java</span>:<span class="number">300</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runner</span><span class="selector-class">.JUnitCore</span>.run(JUnitCore<span class="selector-class">.java</span>:<span class="number">157</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.junit4</span><span class="selector-class">.JUnit4IdeaTestRunner</span>.startRunnerWithArgs(JUnit4IdeaTestRunner<span class="selector-class">.java</span>:<span class="number">117</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.junit4</span><span class="selector-class">.JUnit4IdeaTestRunner</span>.startRunnerWithArgs(JUnit4IdeaTestRunner<span class="selector-class">.java</span>:<span class="number">42</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.rt</span><span class="selector-class">.execution</span><span class="selector-class">.junit</span><span class="selector-class">.JUnitStarter</span>.prepareStreamsAndStart(JUnitStarter<span class="selector-class">.java</span>:<span class="number">262</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.rt</span><span class="selector-class">.execution</span><span class="selector-class">.junit</span><span class="selector-class">.JUnitStarter</span>.main(JUnitStarter<span class="selector-class">.java</span>:<span class="number">84</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke0(Native Method)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke(NativeMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">62</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.DelegatingMethodAccessorImpl</span>.invoke(DelegatingMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">43</span>)</span><br><span class="line">  at java<span class="selector-class">.lang</span><span class="selector-class">.reflect</span><span class="selector-class">.Method</span>.invoke(Method<span class="selector-class">.java</span>:<span class="number">498</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.rt</span><span class="selector-class">.execution</span><span class="selector-class">.application</span><span class="selector-class">.AppMain</span>.main(AppMain<span class="selector-class">.java</span>:<span class="number">147</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>更多元素定位方法可参考官网：</p>
                  <figure class="highlight markdown">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[<span class="string">http://selenium-python.readthedocs.io/locating-elements.html#locating-by-id</span>](<span class="link">http://selenium-python.readthedocs.io/locating-elements.html#locating-by-id</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h5 id="4）chromedriver相关问题"><a href="#4）chromedriver相关问题" class="headerlink" title="4）chromedriver相关问题"></a>4）chromedriver相关问题</h5>
                  <p>在2017年6月微信热更新升级了X5内核之后，真机上切换到WebView上下文环境就出问题了，具体见这篇博文的评论<a href="https://testerhome.com/topics/6954" target="_blank" rel="noopener">Appium 微信 webview 的自动化技术</a> 和 <a href="https://testerhome.com/topics/8990" target="_blank" rel="noopener">Appium 微信小程序，driver.context (“WEBVIEW_com.tencent.mm:tools”) 切换 webview 报错</a> 看评论是通过降低chromedriver版本的方式来避免异常，但是在试过降低版本到20之后还是不行，更新到最新的版本也不行，于是放弃在真机上实现自动化，在模拟器中跑起来的速度也还可以接受。 在真机上跑的时候，切换到WebView上下文环境，程序控制台输出no such session异常，异常信息如下：</p>
                  <figure class="highlight stylus">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.SessionNotFoundException</span>: no such session</span><br><span class="line">   (Driver info: chromedriver=<span class="number">2.21</span>.<span class="number">371459</span> (<span class="number">36</span>d3d07f660ff2bc1bf28a75d1cdabed0983e7c4),platform=Mac OS X <span class="number">10.13</span>.<span class="number">2</span> x86_64) (WARNING: The server did not provide any stacktrace information)</span><br><span class="line"> Command duration or timeout: <span class="number">14</span> milliseconds</span><br><span class="line"> Build info: version: <span class="string">'2.44.0'</span>, revision: <span class="string">'76d78cf'</span>, <span class="selector-tag">time</span>: <span class="string">'2014-10-23 20:02:37'</span></span><br><span class="line"> System info: host: <span class="string">'wenguanchen-MacBook-Pro.local'</span>, ip: <span class="string">'192.168.1.102'</span>, os<span class="selector-class">.name</span>: <span class="string">'Mac OS X'</span>, os<span class="selector-class">.arch</span>: <span class="string">'x86_64'</span>, os<span class="selector-class">.version</span>: <span class="string">'10.13.2'</span>, java<span class="selector-class">.version</span>: <span class="string">'1.8.0_112-release'</span></span><br><span class="line"> Driver info: io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.android</span>.AndroidDriver</span><br><span class="line"> Capabilities [&#123;appPackage=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>, noReset=true, dontStopAppOnReset=true, deviceName=<span class="number">55</span>CDU16C07009329, fullReset=false, platform=LINUX, deviceUDID=<span class="number">55</span>CDU16C07009329, desired=&#123;app=, appPackage=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>, recreateChromeDriverSessions=True, noReset=true, dontStopAppOnReset=true, deviceName=FRD-AL00, fullReset=false, appActivity=<span class="selector-class">.ui</span><span class="selector-class">.LauncherUI</span>, platformVersion=<span class="number">6.0</span>, automationName=Appium, unicodeKeyboard=true, fastReset=false, chromeOptions=&#123;args=[], extensions=[], androidProcess=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>:tools&#125;, platformName=Android, resetKeyboard=true&#125;, platformVersion=<span class="number">6.0</span>, webStorageEnabled=false, automationName=Appium, takesScreenshot=true, javascriptEnabled=true, unicodeKeyboard=true, platformName=Android, resetKeyboard=true, app=, networkConnectionEnabled=true, recreateChromeDriverSessions=True, warnings=&#123;&#125;, databaseEnabled=false, appActivity=<span class="selector-class">.ui</span><span class="selector-class">.LauncherUI</span>, locationContextEnabled=false, fastReset=false, chromeOptions=&#123;args=[], extensions=[], androidProcess=com<span class="selector-class">.tencent</span><span class="selector-class">.mm</span>:tools&#125;&#125;]</span><br><span class="line"> Session ID: e2e50190-<span class="number">398</span>b-<span class="number">4</span>fa2-bc66-db1097201e3f</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeConstructorAccessorImpl</span>.newInstance0(Native Method)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeConstructorAccessorImpl</span>.newInstance(NativeConstructorAccessorImpl<span class="selector-class">.java</span>:<span class="number">62</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.DelegatingConstructorAccessorImpl</span>.newInstance(DelegatingConstructorAccessorImpl<span class="selector-class">.java</span>:<span class="number">45</span>)</span><br><span class="line">  at java<span class="selector-class">.lang</span><span class="selector-class">.reflect</span><span class="selector-class">.Constructor</span>.newInstance(Constructor<span class="selector-class">.java</span>:<span class="number">423</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.ErrorHandler</span>.createThrowable(ErrorHandler<span class="selector-class">.java</span>:<span class="number">204</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.ErrorHandler</span>.throwIfResponseFailed(ErrorHandler<span class="selector-class">.java</span>:<span class="number">162</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.RemoteWebDriver</span>.execute(RemoteWebDriver<span class="selector-class">.java</span>:<span class="number">599</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.DefaultGenericMobileDriver</span>.execute(DefaultGenericMobileDriver<span class="selector-class">.java</span>:<span class="number">27</span>)</span><br><span class="line">  at io<span class="selector-class">.appium</span><span class="selector-class">.java_client</span><span class="selector-class">.AppiumDriver</span>.execute(AppiumDriver<span class="selector-class">.java</span>:<span class="number">272</span>)</span><br><span class="line">  at org<span class="selector-class">.openqa</span><span class="selector-class">.selenium</span><span class="selector-class">.remote</span><span class="selector-class">.RemoteWebDriver</span>.getPageSource(RemoteWebDriver<span class="selector-class">.java</span>:<span class="number">459</span>)</span><br><span class="line">  at com<span class="selector-class">.example</span><span class="selector-class">.AppiumAutoScan</span>.getArticleDetail(AppiumAutoScan<span class="selector-class">.java</span>:<span class="number">238</span>)</span><br><span class="line">  at com<span class="selector-class">.example</span><span class="selector-class">.AppiumAutoScan</span>.launchBrowser(AppiumAutoScan<span class="selector-class">.java</span>:<span class="number">78</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke0(Native Method)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke(NativeMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">62</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.DelegatingMethodAccessorImpl</span>.invoke(DelegatingMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">43</span>)</span><br><span class="line">  at java<span class="selector-class">.lang</span><span class="selector-class">.reflect</span><span class="selector-class">.Method</span>.invoke(Method<span class="selector-class">.java</span>:<span class="number">498</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.model</span>.FrameworkMethod$<span class="number">1</span>.runReflectiveCall(FrameworkMethod<span class="selector-class">.java</span>:<span class="number">45</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.internal</span><span class="selector-class">.runners</span><span class="selector-class">.model</span><span class="selector-class">.ReflectiveCallable</span>.run(ReflectiveCallable<span class="selector-class">.java</span>:<span class="number">15</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.model</span><span class="selector-class">.FrameworkMethod</span>.invokeExplosively(FrameworkMethod<span class="selector-class">.java</span>:<span class="number">42</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.internal</span><span class="selector-class">.runners</span><span class="selector-class">.statements</span><span class="selector-class">.InvokeMethod</span>.evaluate(InvokeMethod<span class="selector-class">.java</span>:<span class="number">20</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.runLeaf(ParentRunner<span class="selector-class">.java</span>:<span class="number">263</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.BlockJUnit4ClassRunner</span>.runChild(BlockJUnit4ClassRunner<span class="selector-class">.java</span>:<span class="number">68</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.BlockJUnit4ClassRunner</span>.runChild(BlockJUnit4ClassRunner<span class="selector-class">.java</span>:<span class="number">47</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span>.ParentRunner$<span class="number">3</span>.run(ParentRunner<span class="selector-class">.java</span>:<span class="number">231</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span>.ParentRunner$<span class="number">1</span>.schedule(ParentRunner<span class="selector-class">.java</span>:<span class="number">60</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.runChildren(ParentRunner<span class="selector-class">.java</span>:<span class="number">229</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.access$<span class="number">000</span>(ParentRunner<span class="selector-class">.java</span>:<span class="number">50</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span>.ParentRunner$<span class="number">2</span>.evaluate(ParentRunner<span class="selector-class">.java</span>:<span class="number">222</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runners</span><span class="selector-class">.ParentRunner</span>.run(ParentRunner<span class="selector-class">.java</span>:<span class="number">300</span>)</span><br><span class="line">  at org<span class="selector-class">.junit</span><span class="selector-class">.runner</span><span class="selector-class">.JUnitCore</span>.run(JUnitCore<span class="selector-class">.java</span>:<span class="number">157</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.junit4</span><span class="selector-class">.JUnit4IdeaTestRunner</span>.startRunnerWithArgs(JUnit4IdeaTestRunner<span class="selector-class">.java</span>:<span class="number">117</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.junit4</span><span class="selector-class">.JUnit4IdeaTestRunner</span>.startRunnerWithArgs(JUnit4IdeaTestRunner<span class="selector-class">.java</span>:<span class="number">42</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.rt</span><span class="selector-class">.execution</span><span class="selector-class">.junit</span><span class="selector-class">.JUnitStarter</span>.prepareStreamsAndStart(JUnitStarter<span class="selector-class">.java</span>:<span class="number">262</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.rt</span><span class="selector-class">.execution</span><span class="selector-class">.junit</span><span class="selector-class">.JUnitStarter</span>.main(JUnitStarter<span class="selector-class">.java</span>:<span class="number">84</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke0(Native Method)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.NativeMethodAccessorImpl</span>.invoke(NativeMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">62</span>)</span><br><span class="line">  at sun<span class="selector-class">.reflect</span><span class="selector-class">.DelegatingMethodAccessorImpl</span>.invoke(DelegatingMethodAccessorImpl<span class="selector-class">.java</span>:<span class="number">43</span>)</span><br><span class="line">  at java<span class="selector-class">.lang</span><span class="selector-class">.reflect</span><span class="selector-class">.Method</span>.invoke(Method<span class="selector-class">.java</span>:<span class="number">498</span>)</span><br><span class="line">  at com<span class="selector-class">.intellij</span><span class="selector-class">.rt</span><span class="selector-class">.execution</span><span class="selector-class">.application</span><span class="selector-class">.AppMain</span>.main(AppMain<span class="selector-class">.java</span>:<span class="number">147</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>在Appium端输出的异常信息如下：</p>
                  <figure class="highlight sql">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[debug] [AndroidDriver] Found webviews: ["WEBVIEW_com.tencent.mm:tools","WEBVIEW_com.tencent.mm"]</span><br><span class="line">[debug] [AndroidDriver] Available contexts: ["NATIVE_APP","WEBVIEW_com.tencent.mm:tools","WEBVIEW_com.tencent.mm"]</span><br><span class="line">[debug] [AndroidDriver] Connecting to chrome-backed webview context 'WEBVIEW_com.tencent.mm:tools'</span><br><span class="line">[debug] [Chromedriver] Changed state to 'starting'</span><br><span class="line">[Chromedriver] <span class="keyword">Set</span> chromedriver <span class="built_in">binary</span> <span class="keyword">as</span>: /Applications/Appium.app/<span class="keyword">Contents</span>/Resources/node_modules/appium/node_modules/appium-android-driver/node_modules/appium-chromedriver/chromedriver/mac/chromedriver</span><br><span class="line">[Chromedriver] Killing <span class="keyword">any</span> <span class="keyword">old</span> chromedrivers, running: pkill <span class="number">-15</span> -f <span class="string">"/Applications/Appium.app/Contents/Resources/node_modules/appium/node_modules/appium-android-driver/node_modules/appium-chromedriver/chromedriver/mac/chromedriver.*--port=9515"</span></span><br><span class="line">[Chromedriver] <span class="keyword">No</span> <span class="keyword">old</span> chromedrivers seemed <span class="keyword">to</span> exist</span><br><span class="line">[Chromedriver] Spawning chromedriver <span class="keyword">with</span>: /Applications/Appium.app/<span class="keyword">Contents</span>/Resources/node_modules/appium/node_modules/appium-android-driver/node_modules/appium-chromedriver/chromedriver/mac/chromedriver <span class="comment">--url-base=wd/hub --port=9515 --adb-port=5037</span></span><br><span class="line">[Chromedriver] [STDOUT] <span class="keyword">Starting</span> ChromeDriver <span class="number">2.21</span><span class="number">.371459</span> (<span class="number">36</span>d3d07f660ff2bc1bf28a75d1cdabed0983e7c4) <span class="keyword">on</span> port <span class="number">9515</span></span><br><span class="line"><span class="keyword">Only</span> <span class="keyword">local</span> connections <span class="keyword">are</span> allowed.</span><br><span class="line">[JSONWP Proxy] Proxying [<span class="keyword">GET</span> /<span class="keyword">status</span>] <span class="keyword">to</span> [<span class="keyword">GET</span> <span class="keyword">http</span>://<span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">9515</span>/wd/hub/<span class="keyword">status</span>] <span class="keyword">with</span> <span class="keyword">no</span> <span class="keyword">body</span></span><br><span class="line">[Chromedriver] [STDERR] [warn] kq_init: detected broken kqueue; not using.: Undefined error: 0</span><br><span class="line">[JSONWP Proxy] Got response <span class="keyword">with</span> <span class="keyword">status</span> <span class="number">200</span>: <span class="string">"&#123;\"</span>sessionId\<span class="string">":\"</span>\<span class="string">",\"</span>stat...</span><br><span class="line">[JSONWP Proxy] Proxying [POST /<span class="keyword">session</span>] <span class="keyword">to</span> [POST <span class="keyword">http</span>://<span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">9515</span>/wd/hub/<span class="keyword">session</span>] <span class="keyword">with</span> <span class="keyword">body</span>: &#123;<span class="string">"desiredCapabilities"</span>:&#123;<span class="string">"ch...</span></span><br><span class="line"><span class="string">[JSONWP Proxy] Got response with status 200: &#123;"</span>sessionId<span class="string">":"</span><span class="number">166</span>cee263fc87...</span><br><span class="line">[debug] [Chromedriver] <span class="keyword">Changed</span> state <span class="keyword">to</span> <span class="string">'online'</span></span><br><span class="line"> [MJSONWP] Responding <span class="keyword">to</span> <span class="keyword">client</span> <span class="keyword">with</span> driver.setContext() <span class="keyword">result</span>: <span class="literal">null</span></span><br><span class="line"> [<span class="keyword">HTTP</span>] &lt;<span class="comment">-- POST /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/context 200 903 ms - 76 </span></span><br><span class="line"> [<span class="keyword">HTTP</span>] <span class="comment">--&gt; GET /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/context &#123;&#125;</span></span><br><span class="line"> [MJSONWP] <span class="keyword">Calling</span> AppiumDriver.getCurrentContext() <span class="keyword">with</span> args: [<span class="string">"82b9d81c-f725-473d-8d55-d...</span></span><br><span class="line"><span class="string"> [MJSONWP] Responding to client with driver.getCurrentContext() result: "</span>WEBVIEW_com.tencent.mm:tools<span class="string">"</span></span><br><span class="line"><span class="string"> [HTTP] &lt;-- GET /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/context 200 2 ms - 102 </span></span><br><span class="line"><span class="string"> [HTTP] --&gt; GET /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/source &#123;&#125;</span></span><br><span class="line"><span class="string"> [MJSONWP] Driver proxy active, passing request on via HTTP proxy</span></span><br><span class="line"><span class="string"> [JSONWP Proxy] Proxying [GET /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/source] to [GET http://127.0.0.1:9515/wd/hub/session/166cee263fc8757cbcb5576a52f7229e/source] with body: &#123;&#125;</span></span><br><span class="line"><span class="string"> [JSONWP Proxy] Got response with status 200: "</span>&#123;\<span class="string">"sessionId\"</span>:\<span class="string">"166cee263...</span></span><br><span class="line"><span class="string"> [JSONWP Proxy] Replacing sessionId 166cee263fc8757cbcb5576a52f7229e with 82b9d81c-f725-473d-8d55-ddbc1f92c100</span></span><br><span class="line"><span class="string"> [HTTP] &lt;-- GET /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/source 200 8 ms - 220</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>如果要替换chromedriver的版本，可以从Appium上输出的Log信息找到chromedriver的路径，在终端依次执行如下命令打开chromedriver所在的文件夹。</p>
                  <figure class="highlight groovy">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">cd <span class="regexp">/Applications/</span>Appium.app<span class="regexp">/Contents/</span>Resources<span class="regexp">/node_modules/</span>appium<span class="regexp">/node_modules/</span>appium-android-driver<span class="regexp">/node_modules/</span>appium-chromedriver<span class="regexp">/chromedriver/</span>mac/</span><br><span class="line">open .</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>相应的chromedriver和Chrome版本对应信息和下载地址可以参考： <a href="https://blog.csdn.net/huilan_same/article/details/51896672" target="_blank" rel="noopener">selenium之 chromedriver与chrome版本映射表</a></p>
                  <h5 id="5）程序使用的JAR包"><a href="#5）程序使用的JAR包" class="headerlink" title="5）程序使用的JAR包"></a>5）程序使用的JAR包</h5>
                  <p>自动化脚本程序要跑起来需要两个压缩包，java-client-3.1.0.jar 和 selenium-server-standalone-2.44.0.jar ，试过使用这两个JAR包的最新版本，会有一些奇奇怪怪的问题，这两个版本的JAR包够用了。 java-client-3.1.0.jar 可以从Appium官网下载：</p>
                  <figure class="highlight markdown">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[<span class="string">http://appium.io/downloads.html</span>](<span class="link">http://appium.io/downloads.html</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>selenium-server-standalone-2.44.0.jar 可以从selenium官网下载：</p>
                  <figure class="highlight markdown">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[<span class="string">http://selenium-release.storage.googleapis.com/index.html</span>](<span class="link">http://selenium-release.storage.googleapis.com/index.html</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h5 id="6）虚拟机"><a href="#6）虚拟机" class="headerlink" title="6）虚拟机"></a>6）虚拟机</h5>
                  <p>我这边使用的是网易MuMu虚拟机，基于Android 4.4.4平台，在我自己的Mac上跑着没问题，同一个版本安装到公司的Mac上就跑不起来，一打开就崩。后面虚拟机自动升级到了Android6.0.1，脚本跑了就有异常，而且每次打开的时候经常卡死在加载页面，system so库报异常。所以最好还是基于Android4.X的版本上运行脚本，Mac上没有一个通用稳定的虚拟机，自己下几个看看是否能用，个人测试各类型的虚拟机结果如下： 1）网易MuMu：在Mac上还是比较好用的，但是最新的版本是6.0.1，初始化经常卡死，无法回退到4.4.4平台版本，脚本在Android6.0平台上切换到WebView的上下文环境异常，升级ChromeDriver版本和Appium版本也无法解决此问题。 2）GenyMotion：微信安装之后无法打开，一直闪退，页面滑动在Mac上巨难操作。 3）天天模拟器：下载的DMG安装文件根本无法打开。 4）夜神模拟器：还是比较好用的，但是Appium adb无法连上虚拟机，从Log来看一直在重启adb， 最后程序中断。 5）逍遥安卓：没有Mac版本。 6）BlueStack：无法安装，安装过程中异常退出，多次重试还是一样。 综上，如果是在Mac上运行虚拟机，目前测试有效的是网易MuMu 基于Android 4.4.4 平台的版本，其他版本和虚拟机都有各种问题。 另：附上Android WebView 历史版本下载地址（需要翻墙）：</p>
                  <figure class="highlight markdown">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[<span class="string">https://cn.apkhere.com/app/com.google.android.webview</span>](<span class="link">https://cn.apkhere.com/app/com.google.android.webview</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>WebView 和对应的ChromeDriver版本见Appium GitHub chromedriver说明文档：</p>
                  <figure class="highlight awk">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[https:<span class="regexp">//gi</span>thub.com<span class="regexp">/appium/</span>appium<span class="regexp">/blob/m</span>aster<span class="regexp">/docs/</span>en<span class="regexp">/writing-running-appium/</span>web<span class="regexp">/chromedriver.md](https:/</span><span class="regexp">/github.com/</span>appium<span class="regexp">/appium/</span>blob<span class="regexp">/master/</span>docs<span class="regexp">/en/</span>writing-running-appium<span class="regexp">/web/</span>chromedriver.md)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h5 id="7）编译IDE"><a href="#7）编译IDE" class="headerlink" title="7）编译IDE"></a>7）编译IDE</h5>
                  <p>不做Android开发的可以下载Eclipse IDE，在Eclipse下运行Java程序还比较方便，拷贝工程源码中的三份文件即可</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">java-client<span class="number">-3.1</span><span class="number">.0</span>.jar </span><br><span class="line">selenium-server-standalone<span class="number">-2.44</span><span class="number">.0</span>.jar  </span><br><span class="line">AppiumWeChatAuto/appiumauto/src/main/java/com/example/AppiumAutoScan.java</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>Eclipse IDE下载地址：</p>
                  <figure class="highlight markdown">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[<span class="string">http://www.eclipse.org/downloads/packages/</span>](<span class="link">http://www.eclipse.org/downloads/packages/</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>Java版本和对应的Eclipse IDE版本参考：</p>
                  <figure class="highlight markdown">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[<span class="string">http://wiki.eclipse.org/Eclipse/Installation</span>](<span class="link">http://wiki.eclipse.org/Eclipse/Installation</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h5 id="8）GitHub工程源码"><a href="#8）GitHub工程源码" class="headerlink" title="8）GitHub工程源码"></a>8）GitHub工程源码</h5>
                  <p><strong>源码GitHub地址：</strong></p>
                  <figure class="highlight markdown">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[<span class="string">https://github.com/wenguan0927/AppiumWeChatAuto</span>](<span class="link">https://github.com/wenguan0927/AppiumWeChatAuto</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>运行Android工程查看设备信息的时候Edit Configurations切换到app，运行自动化脚本的时候切换到AppiumAutoScan。支持按最近一周，一个月，一年或爬取所有历史文章，checkTimeLimit()传入不同限制时间类型的参数即可。</p>
                  <h3 id="四、参考资料"><a href="#四、参考资料" class="headerlink" title="四、参考资料"></a>四、参考资料</h3>
                  <p>Appium 官方文档：<a href="http://appium.io/docs/cn/about-appium/intro/" target="_blank" rel="noopener">http://appium.io/docs/cn/about-appium/intro/</a></p>
                  <p><a href="https://www.cnblogs.com/7chentest/p/6396551.html" target="_blank" rel="noopener">Appium 常用API</a></p>
                  <p><a href="https://blog.csdn.net/qq_19636353/article/details/53731254" target="_blank" rel="noopener">Appium自动化测试–使用Chrome调试模式获取App混合应用H5界面元素</a></p>
                  <p><a href="https://testerhome.com/topics/6954" target="_blank" rel="noopener">Appium 微信 webview 的自动化技术</a></p>
                  <p><a href="https://anikikun.gitbooks.io/appium-girls-tutorial/content/index.html" target="_blank" rel="noopener">Appium Girls 学习手册</a></p>
                  <p><a href="https://cloud.tencent.com/developer/article/1007014" target="_blank" rel="noopener">Appium：轻松玩转app+webview混合应用自动化测试</a></p>
                  <p><a href="https://testerhome.com/topics/8990" target="_blank" rel="noopener">Appium 微信小程序，driver.context (“WEBVIEW_com.tencent.mm:tools”) 切换 webview 报错</a></p>
                  <p><a href="https://blog.csdn.net/TalorSwfit20111208/article/details/71050135" target="_blank" rel="noopener">Appium 事件监听</a></p>
                  <p><a href="https://juejin.im/post/5acb49126fb9a028cc6186f1" target="_blank" rel="noopener">妙用AccessibilityService黑科技实现微信自动加好友拉人进群聊</a></p>
                  <p><a href="https://blog.csdn.net/damnivictory/article/details/54629305" target="_blank" rel="noopener">Appium自动化测试Android</a></p>
                  <p><a href="https://my.oschina.net/outcat/blog/491529?p=2" target="_blank" rel="noopener">Windows下部署Appium教程（Android App自动化测试框架搭建）</a></p>
                  <p><a href="http://bbs.mb.qq.com/thread-243399-1-1.html" target="_blank" rel="noopener">微信、手Q、Qzone之x5内核inspect调试解决方案</a></p>
                  <p><a href="https://blog.csdn.net/huilan_same/article/details/51896672" target="_blank" rel="noopener">selenium之 chromedriver与chrome版本映射表</a></p>
                  <p><a href="https://blog.csdn.net/smallHorseDaDa/article/details/72865685?locationNum=12&amp;fps=1" target="_blank" rel="noopener">（Android开发自测）在Mac OS 10.12 上安装配置appium</a></p>
                  <p><a href="https://www.jianshu.com/p/ef01ce654302" target="_blank" rel="noopener">辅助功能 AccessibilityService笔记</a></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-02 11:08:21" itemprop="dateCreated datePublished" datetime="2019-07-02T11:08:21+08:00">2019-07-02</time>
                </span>
                <span id="/6763.html" class="post-meta-item leancloud_visitors" data-flag-title="微信公众号文章爬取之：微信自动化" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>30k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>28 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6761.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> Python <i class="label-arrow"></i>
                  </a>
                  <a href="/6761.html" class="post-title-link" itemprop="url">微信公众号文章爬取之：服务端数据采集</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>本文转载自：<a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener">陈文管的博客-微信公众号文章爬取之：服务端数据采集</a> 本篇内容介绍微信公众号文章服务端数据爬取的实现，配合上一篇<a href="https://www.chenwenguan.com/wechat-browse-automation/" target="_blank" rel="noopener">微信公众号文章采集之：微信自动化</a>，构成完整的微信公众号文章数据采集系统。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/06/wechat-gzh-spider-system.png" alt="公众号文章爬取系统架构图"></a></p>
                  <p>公众号文章爬取系统架构图</p>
                  <h3 id="一、AnyProxy-配置（Mac）"><a href="#一、AnyProxy-配置（Mac）" class="headerlink" title="一、AnyProxy 配置（Mac）"></a>一、AnyProxy 配置（Mac）</h3>
                  <p>AnyProxy是一个开放式的HTTP代理服务器，官方文档：<a href="http://anyproxy.io/cn/" target="_blank" rel="noopener">http://anyproxy.io/cn/</a> Github主页：<a href="https://github.com/alibaba/anyproxy" target="_blank" rel="noopener">https://github.com/alibaba/anyproxy</a> 主要特性包括： 基于Node.js，开放二次开发能力，允许自定义请求处理逻辑 支持Https的解析 提供GUI界面，用以观察请求</p>
                  <h4 id="1、安装NodeJS"><a href="#1、安装NodeJS" class="headerlink" title="1、安装NodeJS"></a>1、安装NodeJS</h4>
                  <p>在安装Anyproxy之前，需要先安装Nodejs。Nodejs下载地址：<a href="http://nodejs.cn/download/" target="_blank" rel="noopener">http://nodejs.cn/download/</a>。 下载安装完之后可以在终端执行以下命令查看所安装的版本：</p>
                  <figure class="highlight crmsh">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"> <span class="keyword">node</span> <span class="title">--version</span>       查看<span class="keyword">node</span><span class="title">安装版本</span></span><br><span class="line"><span class="title">npm</span> -v               查看npm安装版本</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h4 id="2、AnyProxy安装配置"><a href="#2、AnyProxy安装配置" class="headerlink" title="2、AnyProxy安装配置"></a>2、AnyProxy安装配置</h4>
                  <h5 id="1-Mac端的安装配置"><a href="#1-Mac端的安装配置" class="headerlink" title="1) Mac端的安装配置"></a>1) Mac端的安装配置</h5>
                  <p>AnyProxy 不要安装最新的版本，因为接口变动较大，不便于在原来的基础上重写接口，如果已经安装最新的版本，先执行以下命令卸载：</p>
                  <figure class="highlight coffeescript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">sudo <span class="built_in">npm</span> uninstall -g anyproxy</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>之后安装3.X版本：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">sudo npm install  <span class="symbol">anyproxy@</span><span class="number">3.</span>x  -g</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>接着安装相应的证书：</p>
                  <figure class="highlight ada">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">anyproxy <span class="comment">--root</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h5 id="2-AnyProxy-rule-default-js-文件的配置"><a href="#2-AnyProxy-rule-default-js-文件的配置" class="headerlink" title="2) AnyProxy rule_default.js 文件的配置"></a>2) AnyProxy rule_default.js 文件的配置</h5>
                  <p>直接拷贝如下的配置覆盖AnyProxy rule_default.js配置文件即可，具体可参考知乎大神的文章：<a href="https://zhuanlan.zhihu.com/c_65943221" target="_blank" rel="noopener">微信公众号内容的批量采集与应用</a> ，其中关于图片的优化，配置的fs.readFileSync()参数替换成自己的图片放置路径。将公众号里面的所有图片替换成本地图片的目的是减轻网络传输压力和浏览器占用的内存，有效的提高运行效率，可以自己制作一张1×1像素的png透明图片。 这边跟知乎文章不同的是，在replaceServerResDataAsync中只需要把拦截的微信文章URL地址转发到自己的服务器，因为自动化浏览脚本是直接进入到公众号文章的详情页面，就不需要像知乎文章介绍的那样那么麻烦。 TIPS： 在2019.5.6-2019.5.12时间段之间，微信公众号更新了公众号文章的请求加载方式。 在replaceServerResDataAsync接口中拦截URL的方式已经行不通， 通过AnyProxy拦截的URL参数可以看到已经没有了”/s?__biz=”开头的URL， 但是从</p>
                  <figure class="highlight awk">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">“<span class="regexp">/mp/g</span>etappmsgext?”和“<span class="regexp">/mp/g</span>etappmsgad?“</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>开头的请求链接点击进去还是可以看到文章的请求链接地址。 如果是2019.5.12号之前的时间点，拦截URL接口在replaceServerResDataAsync，对应的AnyProxy rule_default.js配置文件为：<a href="https://github.com/wenguan0927/WechatSpider/blob/master/rule_default_js/rule_default_before20190512.js" target="_blank" rel="noopener">rule_default_before20190512.js</a> 在2019.5.12号之后的时间点，拦截URL的接口变动到shouldUseLocalResponse : function(req,reqBody)，只要把request body发送到后台服务器，再加上”<a href="https://mp.weixin.qq.com/s?”前缀进行拼接就行，对应的AnyProxy" target="_blank" rel="noopener">https://mp.weixin.qq.com/s?”前缀进行拼接就行，对应的AnyProxy</a> rule_default.js配置文件应该改为：<a href="https://github.com/wenguan0927/WechatSpider/blob/master/rule_default_js/rule_default_after20190512.js" target="_blank" rel="noopener">rule_default_after20190512.js</a> 如果忘记了AnyProxy的安装路径，用命令查找rule_default.js文件即可：</p>
                  <figure class="highlight nginx">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="attribute">find</span> <span class="regexp">~ -iname</span> <span class="string">"rule_default.js"</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h5 id="3）AnyProxy启动"><a href="#3）AnyProxy启动" class="headerlink" title="3）AnyProxy启动"></a>3）AnyProxy启动</h5>
                  <p>在终端执行命令启动AnyProxy：</p>
                  <figure class="highlight ebnf">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="attribute">anyproxy -i</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>如果遇到如下的异常说明缺少写文件夹的权限：</p>
                  <figure class="highlight crystal">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">the default rule <span class="keyword">for</span> AnyProxy.</span><br><span class="line">Anyproxy rules initialize finished, have <span class="function"><span class="keyword">fun</span>!</span></span><br><span class="line"><span class="function"><span class="title">The</span></span> WebSocket will not work properly in the https intercept mode :(</span><br><span class="line">fs.<span class="symbol">js:</span><span class="number">885</span></span><br><span class="line">  <span class="keyword">return</span> binding.mkdir(pathModule._makeLong(path),</span><br><span class="line">                 ^</span><br><span class="line"><span class="symbol">Error:</span> <span class="symbol">EACCES:</span> permission denied, mkdir <span class="string">'/Users/chenwenguan/.anyproxy/cache_r929590'</span></span><br><span class="line">     at Object.fs.mkdirSync (fs.<span class="symbol">js:</span><span class="number">885</span>:<span class="number">18</span>)</span><br><span class="line">     at Object<span class="class">.<span class="keyword">module</span>.<span class="title">exports</span>.<span class="title">generateCacheDir</span> (/<span class="title">Users</span>/<span class="title">chenwenguan</span>/.<span class="title">nvm</span>/<span class="title">versions</span>/<span class="title">node</span>/<span class="title">v8</span>.9.3/<span class="title">lib</span>/<span class="title">node_modules</span>/<span class="title">anyproxy</span>/<span class="title">lib</span>/<span class="title">util</span>.<span class="title">js</span>:54:8)</span></span><br><span class="line">     at new Recorder (<span class="regexp">/Users/chenwenguan</span><span class="regexp">/.nvm/versions</span><span class="regexp">/node/v</span>8.<span class="number">9.3</span>/<span class="class"><span class="keyword">lib</span>/<span class="title">node_modules</span>/<span class="title">anyproxy</span>/<span class="title">lib</span>/<span class="title">recorder</span>.<span class="title">js</span>:16:31)</span></span><br><span class="line">     at /Users/chenwenguan/.nvm/versions/node/v8.<span class="number">9.3</span>/<span class="class"><span class="keyword">lib</span>/<span class="title">node_modules</span>/<span class="title">anyproxy</span>/<span class="title">proxy</span>.<span class="title">js</span>:116:43</span></span><br><span class="line">     at ChildProcess.exithandler (child_process.<span class="symbol">js:</span><span class="number">282</span>:<span class="number">5</span>)</span><br><span class="line">     at emitTwo (events.<span class="symbol">js:</span><span class="number">126</span>:<span class="number">13</span>)</span><br><span class="line">     at ChildProcess.emit (events.<span class="symbol">js:</span><span class="number">214</span>:<span class="number">7</span>)</span><br><span class="line">     at maybeClose (internal/child_process.<span class="symbol">js:</span><span class="number">925</span>:<span class="number">16</span>)</span><br><span class="line">     at Socket.stream.socket.on (internal/child_process.<span class="symbol">js:</span><span class="number">346</span>:<span class="number">11</span>)</span><br><span class="line">    at emitOne (events.<span class="symbol">js:</span><span class="number">116</span>:<span class="number">13</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>用以下命令修改下文件夹权限即可：</p>
                  <figure class="highlight autohotkey">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">sudo chown -R `whoami` /Users/chenwenguan/.anyproxy</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h5 id="4）Android虚拟机上的配置"><a href="#4）Android虚拟机上的配置" class="headerlink" title="4）Android虚拟机上的配置"></a>4）Android虚拟机上的配置</h5>
                  <p>AnyProxy启动完成后，访问GUI地址：<a href="http://192.168.1.101:8002/" target="_blank" rel="noopener">http://192.168.1.101:8002</a></p>
                  <p><a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/10/anyproxy-download-rootca-1024x168.png" alt="下载AnyProxy证书文件"></a></p>
                  <p>下载AnyProxy证书文件</p>
                  <p>点击下载rootCA.crt文件，可以在虚拟机SD卡根目录下新建一个rootCA文件夹，把文件用adb命令的方式Push到虚拟机的sdcard目录下：</p>
                  <figure class="highlight gradle">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">adb <span class="keyword">push</span> rootCA.crt <span class="regexp">/sdcard/</span>rootCA<span class="regexp">/</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>之后进入Android虚拟机系统设置界面，进入安全设置项，选择从SD卡安装（从SD卡安装证书）设置项，选择Push到sd卡下的证书文件安装，如果没有做这个操作，在微信加载WebView的时候会不断地弹出警告弹窗。 如果没有在模拟器找到系统设置或WI-FI网络设置的入口，可用adb命令调用进入，直接进入网络设置页面命令如下：</p>
                  <figure class="highlight stylus">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">adb shell am start -<span class="selector-tag">a</span> android<span class="selector-class">.intent</span><span class="selector-class">.action</span><span class="selector-class">.MAIN</span> -n com<span class="selector-class">.android</span>.settings/<span class="selector-class">.wifi</span>.WifiSettings</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>进入模拟器系统设置页面命令：</p>
                  <figure class="highlight vim">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">adb <span class="keyword">shell</span> <span class="keyword">am</span> start <span class="keyword">com</span>.android.settings/<span class="keyword">com</span>.android.settings.Settings</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>在Android模拟器上还要设置网络代理，长按WIFI网络设置项，弹窗选择修改网络选项，IP地址就写电脑的IP，端口填8001。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/10/android-device-monitor-network-proxy-1024x646.png" alt="安卓虚拟机网络代理设置"></a></p>
                  <p>安卓虚拟机网络代理设置</p>
                  <p>在以上都配置完毕之后，进入微信应用查看公众号文章，就可以在GUI界面上看到AnyProxy拦截到的所有请求URL地址信息。 如文章前面的说明，在2019.5.12时间点之前还可以看到”/s?__biz=”开头的URL请求参数。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/10/anyproxy-gui-wechat-proxy-url-1024x567.png" alt="AnyProxy 拦截的URL信息"></a></p>
                  <p>AnyProxy 拦截的URL信息</p>
                  <p>上面/s?__biz=开头的URL就是微信公众号文章详细的URL地址，可以点击查看具体的详细信息：</p>
                  <p><a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/10/anyproxy-gui-wechat-proxy-url-detail-1024x567.png" alt="微信公众号文章URL详细信息"></a></p>
                  <p>微信公众号文章URL详细信息</p>
                  <p>页面往下滑动，查看请求到的公众号文章详细字段信息，服务端爬虫就是从这些字段参数定义的值来截取需要的信息。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/10/anyproxy-gui-wechat-post-detail-1024x567.png" alt="AnyProxy解析的公众号文章详细信息"></a></p>
                  <p>AnyProxy解析的公众号文章详细信息</p>
                  <p>目前在服务端实现保存的字段只是一些基本的信息，如标题、作者、文章发布时间等，如果需要其他信息可以参考上图的一些字段做正则匹配。 在2015.5.12时间点，微信变动公众号文章加载方式之后，文章的实际地址参数在“/mp/getappmsgext?”开头的请求链接里面，包括点赞和阅读数据也在这个请求返回的结构体里面。在“ /mp/getappmsgad?“开头的请求链接request body也是文章的链接地址，但选择“/mp/getappmsgext?”开头的URL来拦截处理比较好。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/04/wechat-spider-getappmsgext-intercept-request-body-1024x481.jpg" alt="拦截getappmsgext的请求结构体就是文章实际地址"></a></p>
                  <p>拦截getappmsgext的请求结构体就是文章实际地址</p>
                  <p>在getappmsgext拦截的页面往下滑动到response body就可以看到文章的阅读和点赞数据，因为这边没有阅读和点赞的数据解析需求，有需要的自行研究下从rule_default.js配置文件哪个接口拦截转发数据。</p>
                  <p><a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/04/wechat-spider-getappmsgext-intercept-read-like-1024x488.jpg" alt="拦截getappmsgext的请求返回的数据包括阅读和点赞数"></a></p>
                  <p>拦截getappmsgext的请求返回的数据包括阅读和点赞数</p>
                  <h3 id="二、JavaWeb-服务端实现"><a href="#二、JavaWeb-服务端实现" class="headerlink" title="二、JavaWeb 服务端实现"></a>二、JavaWeb 服务端实现</h3>
                  <h4 id="1、运行环境配置"><a href="#1、运行环境配置" class="headerlink" title="1、运行环境配置"></a>1、运行环境配置</h4>
                  <p>Intellij IDEA 官网下载地址：<a href="https://www.jetbrains.com/idea/" target="_blank" rel="noopener">https://www.jetbrains.com/idea/</a> 破解方法参考：<a href="https://blog.csdn.net/u013673242/article/details/72243066?utm_source=itdadao&amp;utm_medium=referral" target="_blank" rel="noopener">IntelliJ IDEA 2017 完美注册方法</a> <strong>TIPS</strong>：要先打开IDEA之后再做如下配置，否则会被识别为文件已损坏</p>
                  <figure class="highlight groovy">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">-<span class="string">javaagent:</span><span class="regexp">/Applications/</span>IntelliJ IDEA.app<span class="regexp">/Contents/</span>bin/JetbrainsCrack<span class="number">-2.7</span>-release-str.jar</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h4 id="2、服务端实现"><a href="#2、服务端实现" class="headerlink" title="2、服务端实现"></a>2、服务端实现</h4>
                  <p><strong>爬虫服务端实现GitHub源码地址：</strong></p>
                  <figure class="highlight markdown">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[<span class="string">https://github.com/wenguan0927/WechatSpider</span>](<span class="link">https://github.com/wenguan0927/WechatSpider</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h5 id="1）实现类说明"><a href="#1）实现类说明" class="headerlink" title="1）实现类说明"></a>1）实现类说明</h5>
                  <p><a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/10/intellij-idea-wechat-spider-class-254x300.png" alt="公众号爬虫服务端实现源码类说明"></a></p>
                  <p>公众号爬虫服务端实现源码类说明</p>
                  <p>WechatController类做AnyProxy转发的文章链接接收和JSP页面显示的逻辑处理。 mapper文件夹下的两个类是数据库操作的映射操作类，通过配置文件自动生成，只是手动加了几个数据查询方法的实现，PostKeyWordMapper用来操作存储公众号文章关键词的数据，WechatPostMapper用来操作存储公众号文章详细数据。 model文件夹下PostJSP只是用来JSP页面显示数据的一个中间类，在JSP页面中去拼接包含较多特殊字符的文本内容容易出问题，我这边的实现是要直接生成MarkDown文档的格式，所以做了一层转化处理。PostKeyWord是公众号关键字的类，WechatPost是公众号文章详细数据类。 spider文件夹下的类就是公众号文章关键字和公众号文章详细信息的爬取解析处理类。 util文件夹下放的是工具类，SimHash只是用来测试通过关键字计算公众号文章关联性实现类，有兴趣可以自己做下挖掘。</p>
                  <h5 id="2）配置文件说明"><a href="#2）配置文件说明" class="headerlink" title="2）配置文件说明"></a>2）配置文件说明</h5>
                  <p><a href="https://www.chenwenguan.com/wechat-spider-server/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2018/10/intellij-idea-wechat-spider-resources-288x300.png" alt="公众号爬虫服务端实现配置文件说明"></a></p>
                  <p>公众号爬虫服务端实现配置文件说明</p>
                  <p>mybatis-mapper文件夹下的两个文件是数据库映射XML资源文件，通过generator.properties和generatorConfig.xml两个配置文件自动生成，具体可参考：<a href="https://www.cnblogs.com/kangoroo/p/7495873.html" target="_blank" rel="noopener">数据库表反向生成(一) MyBatis-generator与IDEA的集成</a> 。 这边需要注意的是，如果要在反向生成的数据库映射操作文件中增加方法实现，不要在Mapper.xml文件里面添加方法，要加的话在Mapper.java的类中加，可参考WechatPostMapper.java 类中末尾几个方法，通过在函数上添加注解的方式实现。 generator.properties文件中的jdbc.driverLocation改成自己电脑的connector实际路径，jdbc.userId和jdbc.password改成自己数据库的用户名和密码。 jdbc.properties文件中的数据库参数也改成自己配置的值。 其他文件只是常规的Web实现配置，此处不做多余赘述。</p>
                  <h5 id="3）实现过程中遇到的问题"><a href="#3）实现过程中遇到的问题" class="headerlink" title="3）实现过程中遇到的问题"></a>3）实现过程中遇到的问题</h5>
                  <p>1）@Autowired注解的Mapper类报NullPointException异常</p>
                  <figure class="highlight aspectj">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> WechatPostMapper wechatPostMapper;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">     <span class="keyword">private</span> PostKeywordMapper postKeywordMapper;</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>这边需要注意的是通过@Autowired注解声明的类不能在一个new出来的类中使用，@Autowired只能在通过框架注解生成的类中使用，在一个new出来的类中使用注解在框架生成的类中是找不到的，所以会报空指针异常。其他异常可参考：<a href="https://www.jianshu.com/p/0e0421c81e6a" target="_blank" rel="noopener">@Autowired注解和静态方法</a> 2）Intellj（IDEA） warning no artifacts configured 异常 参考文章：<a href="https://blog.csdn.net/small_mouse0/article/details/77506060" target="_blank" rel="noopener">【错误解决】Intellj（IDEA） warning no artifacts configured</a> 3）Intellij 代理端口占用异常</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">错误: 代理抛出异常错误: </span><br><span class="line">java.rmi.server.ExportException:<span class="built_in"> Port </span>already <span class="keyword">in</span> use: 1099; nested exception is: </span><br><span class="line">java.net.BindException:<span class="built_in"> Address </span>already <span class="keyword">in</span> use</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>终端输入命令查看端口所在进程：</p>
                  <figure class="highlight angelscript">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">sudo lsof -i :<span class="number">1099</span></span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>之后可看到如下类似的结果：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">COMMAND PID       <span class="built_in"> USER </span>  FD  <span class="built_in"> TYPE </span>            DEVICE SIZE/OFF NODE NAME</span><br><span class="line">java    582 chenwenguan   23u <span class="built_in"> IPv6 </span>0x38b6c6251709a7d3      0t0  TCP *:rmiregistry (LISTEN)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>终端输命令杀进程：kill 582 4）<a href="http://java.sun.com/jsp/jstl/core" target="_blank" rel="noopener">http://java.sun.com/jsp/jstl/core</a> cannot be resolved 如果配置的jstl版本是1.2， 不需要通过导入jstl.jar和standard.jar包的方式，如果配置的是1.2以下的版本，可参考文章： <a href="https://blog.csdn.net/BlueBlueSkyZ/article/details/54926096" target="_blank" rel="noopener">core cannot be resolved</a>。 jar包的下载地址：</p>
                  <figure class="highlight markdown">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">[<span class="string">http://archive.apache.org/dist/jakarta/taglibs/standard/binaries/</span>](<span class="link">http://archive.apache.org/dist/jakarta/taglibs/standard/binaries/</span>)</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>5） Warning:The/usr/local/mysql/data directory is not owned by the ‘mysql’ or ‘_mysql’</p>
                  <p>如果因Mac系统更新导致MySQL提示以上异常，执行以下命令解决：</p>
                  <figure class="highlight groovy">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">sudo chown -R  <span class="string">_mysql:</span>wheel  <span class="regexp">/usr/</span>local<span class="regexp">/mysql/</span>data</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>参考博文：<a href="https://blog.csdn.net/lvsijian8/article/details/60779386" target="_blank" rel="noopener">Mac在偏好设置启动MySQL失败</a> 6）注解中的数据库IN查询语句实现</p>
                  <figure class="highlight less">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line"><span class="variable">@Select</span>(&#123;<span class="string">"&lt;script&gt;"</span>,</span><br><span class="line">          <span class="string">"select"</span>,</span><br><span class="line">          <span class="string">"id, biz, appmsgid, title, digest, contenturl, sourceurl, cover, datetime, readnum, "</span>,</span><br><span class="line">          <span class="string">"likenum, isspider, author, nickname, weight, posttype, content"</span>,</span><br><span class="line">          <span class="string">"from postTable where nickname in "</span>,</span><br><span class="line">          <span class="string">"&lt;foreach item='item' collection='nickname' open='(' close=')' separator=','&gt;"</span>,</span><br><span class="line">          <span class="string">"#&#123;item&#125;"</span>,</span><br><span class="line">          <span class="string">"&lt;/foreach&gt;"</span>,</span><br><span class="line">          <span class="string">" and datetime &gt;=#&#123;datetime,jdbcType=TIMESTAMP&#125;"</span>,</span><br><span class="line">          <span class="string">"order by weight DESC"</span>,</span><br><span class="line">          <span class="string">"&lt;/script&gt;"</span></span><br><span class="line"> &#125;)</span><br><span class="line"> <span class="variable">@ResultMap</span>(<span class="string">"ResultMapWithBLOBs"</span>)</span><br><span class="line">List&lt;WechatPost&gt; getATAPosts(<span class="variable">@Param</span>(<span class="string">"nickname"</span>) List&lt;String&gt; nickname, <span class="variable">@Param</span>(<span class="string">"datetime"</span>) Date time);</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>如果是要在注解中实现IN多条件查询，需要如上面的方式去实现，直接按照原生SQL语句的方式实现是行不通的。 参考博文：<a href="https://www.cnblogs.com/a8457013/p/9097742.html" target="_blank" rel="noopener">SpringBoot使用Mybatis注解开发教程-分页-动态sql</a></p>
                  <h5 id="4-数据库实现"><a href="#4-数据库实现" class="headerlink" title="4) 数据库实现"></a>4) 数据库实现</h5>
                  <p>公众号文章详情数据表实现：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">CREATE TABLE `postTable` (</span><br><span class="line">  `id` int(11) <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT,</span><br><span class="line">  `biz` tinytext,</span><br><span class="line">  `appmsgid` tinytext,</span><br><span class="line">  `title` tinytext,</span><br><span class="line">  `digest` longtext,</span><br><span class="line">  `contenturl` longtext,</span><br><span class="line">  `sourceurl` longtext,</span><br><span class="line">  `cover` longtext,</span><br><span class="line">  `datetime` datetime<span class="built_in"> DEFAULT </span><span class="literal">NULL</span>,</span><br><span class="line">  `readnum` int(11)<span class="built_in"> DEFAULT </span><span class="literal">NULL</span>,</span><br><span class="line">  `likenum` int(11)<span class="built_in"> DEFAULT </span><span class="literal">NULL</span>,</span><br><span class="line">  `isspider` int(11)<span class="built_in"> DEFAULT </span><span class="literal">NULL</span>,</span><br><span class="line">  `author` tinytext,</span><br><span class="line">  `nickname` tinytext,</span><br><span class="line">  `weight` int(11)<span class="built_in"> DEFAULT </span><span class="literal">NULL</span>,</span><br><span class="line">  `posttype` int(11)<span class="built_in"> DEFAULT </span><span class="literal">NULL</span>,</span><br><span class="line">  `content` longtext,</span><br><span class="line">  PRIMARY KEY (`id`)</span><br><span class="line">) <span class="attribute">ENGINE</span>=InnoDB <span class="attribute">AUTO_INCREMENT</span>=199<span class="built_in"> DEFAULT </span><span class="attribute">CHARSET</span>=utf8</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <p>公众号关键字数据表实现：</p>
                  <figure class="highlight routeros">
                    <table>
                      <tr>
                        <td class="gutter">
                          <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre>
                        </td>
                        <td class="code">
                          <pre><span class="line">CREATE TABLE `keywordTable` (</span><br><span class="line">  `id` int(11) <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT,</span><br><span class="line">  `wordtext` varchar(45)<span class="built_in"> DEFAULT </span><span class="literal">NULL</span>,</span><br><span class="line">  `wordfrequency` int(11)<span class="built_in"> DEFAULT </span><span class="literal">NULL</span>,</span><br><span class="line">  `wordtype` int(11)<span class="built_in"> DEFAULT </span><span class="literal">NULL</span>,</span><br><span class="line">  PRIMARY KEY (`id`)</span><br><span class="line">) <span class="attribute">ENGINE</span>=InnoDB <span class="attribute">AUTO_INCREMENT</span>=3525<span class="built_in"> DEFAULT </span><span class="attribute">CHARSET</span>=utf8</span><br></pre>
                        </td>
                      </tr>
                    </table>
                  </figure>
                  <h5 id="5）遗留问题"><a href="#5）遗留问题" class="headerlink" title="5）遗留问题"></a>5）遗留问题</h5>
                  <p>公众号文章的分类目前没有很好地实现，也就是，目前爬取的公众号文章我要分为三大类，新闻类、Android开发、技术扩展，一开始的想法是根据以往发布的每周技术周报文章内容，提取每个类别文章的关键词数据，生成一个关键词数据库，之后爬取的文章，可以通过提取文章的关键词跟历史记录文章的关键词词库进行对比，计算它们的相关性来进行归类。 目前用HanLP开源代码来做测试，提取的关键词都是中文的关键词，在做关联性计算的时候并不能达到理想的效果，因为开发类的文章有很多英文的词汇，HanLP里面并不包括英文词汇的词库，所以下一步要做的是做一个技术类文章的分词词库来实现文章的归类处理。 这边给出一些参考文章的链接资源，有兴趣可以自己做一下深挖。 <a href="http://www.hankcs.com/nlp/textrank-algorithm-to-extract-the-keywords-java-implementation.html" target="_blank" rel="noopener">TextRank算法提取关键词的Java实现</a> <a href="http://xiaosheng.me/2017/04/08/article49/" target="_blank" rel="noopener">TextRank算法提取关键词和摘要</a> <a href="https://blog.csdn.net/whzhaochao/article/details/50637300" target="_blank" rel="noopener">计算两组标签/关键词相似度算法</a> <a href="http://www.hankcs.com/nlp/hanlp.html" target="_blank" rel="noopener">HanLP自然语言处理包开源</a> <a href="https://zhuanlan.zhihu.com/p/33605700" target="_blank" rel="noopener">文本关键词提取算法解析</a> <a href="https://www.cnblogs.com/xlturing/p/6136690.html" target="_blank" rel="noopener">NLP点滴——文本相似度</a> <a href="https://www.cnblogs.com/shaosks/p/9121774.html" target="_blank" rel="noopener">文本相似性计算总结（余弦定理，simhash）及代码</a> <a href="https://segmentfault.com/a/1190000013466886" target="_blank" rel="noopener">如何实现一个基本的微信文章分类器</a> <a href="https://github.com/hankcs/HanLP" target="_blank" rel="noopener">HanLP GitHub开源代码</a></p>
                  <h3 id="三、其他参考资料"><a href="#三、其他参考资料" class="headerlink" title="三、其他参考资料"></a>三、其他参考资料</h3>
                  <p><a href="https://www.cnblogs.com/guogangj/p/3725371.html" target="_blank" rel="noopener">Mac OS X上IntelliJ IDEA 13与Tomcat 8的Java Web开发环境搭建</a> <a href="https://www.cnblogs.com/wql025/p/5215570.html" target="_blank" rel="noopener">IntelliJ IDEA 15 创建maven项目</a> <a href="http://www.mybatis.org/mybatis-3/zh/index.html" target="_blank" rel="noopener">MyBatis官网</a> <a href="http://www.runoob.com/w3cnote/intellij-idea-usage.html" target="_blank" rel="noopener">Intellij IDEA 使用教程</a> <a href="https://liuxufei.com/weblog/jishu/71.html" target="_blank" rel="noopener">HTML语言中括号(尖括号)的字符编码</a> <a href="https://segmentfault.com/a/1190000010383923" target="_blank" rel="noopener">mac下mongodb的安装与配置</a> <a href="https://www.jianshu.com/p/2d0a1ecd0c82" target="_blank" rel="noopener">mac下mongodb的安装和使用(使用终端操作)</a> <a href="https://github.com/dboissier/mongo4idea" target="_blank" rel="noopener">Intellij Mongo配置</a> <a href="https://www.cnblogs.com/lwy19998273333/p/5793141.html" target="_blank" rel="noopener">Java连接MongoDB进行增删改查</a> <a href="https://www.cnblogs.com/jeffen/p/6027970.html" target="_blank" rel="noopener">IntelliJ IDEA手动配置连接MySQL数据库</a> <a href="http://www.mongodb.org.cn/" target="_blank" rel="noopener">MongoDB中文教程</a> <a href="https://docs.mongodb.com/manual/core/document/" target="_blank" rel="noopener">MongoDB官方文档</a> <a href="http://webmagic.io/docs/zh/" target="_blank" rel="noopener">WebMagic 爬虫框架</a></p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-02 11:06:34" itemprop="dateCreated datePublished" datetime="2019-07-02T11:06:34+08:00">2019-07-02</time>
                </span>
                <span id="/6761.html" class="post-meta-item leancloud_visitors" data-flag-title="微信公众号文章爬取之：服务端数据采集" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>8.4k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>8 分钟</span>
                </span>
              </div>
            </article>
            <article itemscope itemtype="http://schema.org/Article" class="post-block index" lang="zh-CN">
              <link itemprop="mainEntityOfPage" href="https://cuiqingcai.com/6759.html">
              <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
                <meta itemprop="image" content="/images/avatar.png">
                <meta itemprop="name" content="崔庆才">
                <meta itemprop="description" content="崔庆才的个人站点，记录生活的瞬间，分享学习的心得。">
              </span>
              <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
                <meta itemprop="name" content="静觅">
              </span>
              <header class="post-header">
                <h2 class="post-title" itemprop="name headline">
                  <a class="label"> 技术杂谈 <i class="label-arrow"></i>
                  </a>
                  <a href="/6759.html" class="post-title-link" itemprop="url">2019 WordPress 必备插件推荐</a>
                </h2>
              </header>
              <div class="post-body" itemprop="articleBody">
                <div class="thumb">
                  <img itemprop="contentUrl" class="random">
                </div>
                <div class="excerpt">
                  <p>
                  <p>本文转载自：<a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener">陈文管的博客-2019 WordPress必备插件推荐</a> 推荐下 WordPress 必备的插件，插件的使用主要从 SEO、安全、营销、UI设计、访问数据分析几个方面去考虑，当然并不是插件安装越多越好，过多的插件会拖慢网站加载速度，只在满足需求的前提下安装必要的一些插件。 此外在安装插件启用之前最好先备份下网站数据，很可能因为版本不兼容的原因导致数据库读写异常，这样整个网站就挂了，以防万一，每次安装或者更新插件之前先备份下网站数据。</p>
                  <h3 id="1、Yoast-SEO"><a href="#1、Yoast-SEO" class="headerlink" title="1、Yoast SEO"></a>1、<a href="https://wordpress.org/plugins/wordpress-seo/" target="_blank" rel="noopener">Yoast SEO</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-yoast-seo-300x119.png" alt="Yoast SEO插件"></a></p>
                  <p>Yoast SEO插件</p>
                  <p>做网站SEO必不可少的插件， 可以设置谷歌、Bing、百度、Yandex 的验证码，可以检测文章可读性和SEO的好坏等等，但要记得把分类和TAG的索引关闭，避免内部链接的内容重复。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/plugin-yoast-seo-category-noindex-300x193.jpg" alt="Yoast SEO分类目录声明成noindex,nofollow"></a></p>
                  <p>Yoast SEO分类目录声明成noindex,nofollow</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/plugin-yoast-seo-tag-noindex-300x222.jpg" alt="Yoast SEO 标签目录声明成noindex,nofollow"></a></p>
                  <p>Yoast SEO 标签目录声明成noindex,nofollow</p>
                  <p>如果是要编辑robot.txt，入口在工具=&gt;文件编辑器里面，记得在一个网站没有建好之前要robots.txt设置成：</p>
                  <p>User-agent: *</p>
                  <p>Disallow: /</p>
                  <h3 id="2、WP-Fastest-Cache"><a href="#2、WP-Fastest-Cache" class="headerlink" title="2、WP Fastest Cache"></a>2、<a href="https://wordpress.org/plugins/wp-fastest-cache/" target="_blank" rel="noopener">WP Fastest Cache</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-wp-fastest-cache-300x118.jpg" alt="WP Fastest Cache插件"></a></p>
                  <p>WP Fastest Cache插件</p>
                  <p>压缩HTML，合并CSS，提高网站加载速度，可以直接在插件里面设置CDN加速等等，缓存的设置按照下面截图照搬就行。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wp-fastest-cache-setting-guide-1024x788.jpg" alt="WP Fastest Cache 缓存设置"></a></p>
                  <p>WP Fastest Cache 缓存设置</p>
                  <h3 id="3、Smush-Image-Compression-and-Optimization"><a href="#3、Smush-Image-Compression-and-Optimization" class="headerlink" title="3、Smush Image Compression and Optimization"></a>3、<a href="https://wordpress.org/plugins/wp-smushit/" target="_blank" rel="noopener">Smush Image Compression and Optimization</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-smush-image-compression-300x124.jpg" alt="Smush Image Compression and Optimization插件"></a></p>
                  <p>Smush Image Compression and Optimization插件</p>
                  <p>帮助自动压缩网站的图片，避免图片过大拖慢网站的加载速度，配合TinyPNG(<a href="https://tinypng.com/" target="_blank" rel="noopener">https://tinypng.com/</a>)一起使用更好，每次上传图片之前最好先经过<a href="https://tinypng.com/" target="_blank" rel="noopener">TinyPNG</a>压缩一遍之后再上传，不得不说目前在图片的压缩方面，<a href="https://tinypng.com/" target="_blank" rel="noopener">TinyPNG</a>无疑是最好的。</p>
                  <h3 id="4、Google-XML-Sitemaps"><a href="#4、Google-XML-Sitemaps" class="headerlink" title="4、Google XML Sitemaps"></a>4、<a href="https://wordpress.org/plugins/google-sitemap-generator/" target="_blank" rel="noopener">Google XML Sitemaps</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-google-xml-sitemaps-300x118.jpg" alt="Google XML Sitemaps插件"></a></p>
                  <p>Google XML Sitemaps插件</p>
                  <p>虽然Yoast SEO插件自带网站地图的设置，但是如果要提高配置的可控性，使用Google XML Sitemaps会好点，在提交到Google Search Console收录之后出现一些链接地址无法索引的情况，后面就把HTML网站地图功能关闭掉就好了。</p>
                  <h3 id="5、Clicky-for-WordPress"><a href="#5、Clicky-for-WordPress" class="headerlink" title="5、Clicky for WordPress"></a>5、<a href="https://wordpress.org/plugins/clicky/" target="_blank" rel="noopener">Clicky for WordPress</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-clicky-by-yoast-300x118.jpg" alt="Clicky by Yoast插件"></a></p>
                  <p>Clicky by Yoast插件</p>
                  <p>虽然在网站访问数据分析工具上已经有<a href="https://search.google.com/search-console/about" target="_blank" rel="noopener">Google Search Console</a>和<a href="https://analytics.google.com/" target="_blank" rel="noopener">Google Analytics</a>，但是Clicky(<a href="https://clicky.com/" target="_blank" rel="noopener">https://clicky.com/</a>)工具更能实时地记录反馈网站的访问数据。在<a href="https://clicky.com/" target="_blank" rel="noopener">Clicky</a>注册账号之后，把使用偏好里面的Site ID、Site Key和Admin site key参数填到<a href="https://wordpress.org/plugins/clicky/" target="_blank" rel="noopener">Clicky插件</a>设置里面，之后就可以记录网站的数据。</p>
                  <h3 id="6、Shortcodes-Ultimate"><a href="#6、Shortcodes-Ultimate" class="headerlink" title="6、Shortcodes Ultimate"></a>6、<a href="https://wordpress.org/plugins/shortcodes-ultimate/" target="_blank" rel="noopener">Shortcodes Ultimate</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-shortcodes-ultimate-300x118.jpg" alt="Shortcodes Ultimate插件"></a></p>
                  <p>Shortcodes Ultimate插件</p>
                  <p>对于WordPress里面自定义的样式，简码插件提供了很多样例，省去了手动编辑的麻烦。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/shortcodes-ultimate-plugin-samples-2-300x130.jpg" alt="终极简码样例"></a></p>
                  <p>终极简码样例</p>
                  <h3 id="7、Genesis-Simple-Edits和Genesis-Super-Customizer"><a href="#7、Genesis-Simple-Edits和Genesis-Super-Customizer" class="headerlink" title="7、Genesis Simple Edits和Genesis Super Customizer"></a>7、<a href="https://wordpress.org/plugins/genesis-simple-edits/" target="_blank" rel="noopener">Genesis Simple Edits</a>和<a href="https://wordpress.org/plugins/genesis-super-customizer/" target="_blank" rel="noopener">Genesis Super Customizer</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-genesis-simple-edits-300x117.jpg" alt="Genesis Simple Edits插件"></a></p>
                  <p>Genesis Simple Edits插件</p>
                  <p>如果是使用<a href="https://my.studiopress.com/themes/" target="_blank" rel="noopener">Genesis Framework</a>主题 ，Genesis Simple Edits是必不可少的插件，如果手动修改Genesis Framework主题代码会出异常，甚至会导致WordPress账户后台无法登陆，之前尝试过在主题类中增加几行代码修改样式，后面导致整个服务异常，无法登陆，所以，最好不要手动修改Genesis Framework主题的代码，通过插件去修改保险点。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/genesis-simple-edits-custom-new-footer-1024x94.jpg" alt="Simple Edits实现的自定义底部栏"></a></p>
                  <p>Simple Edits实现的自定义底部栏</p>
                  <p>如果是要实现上面的底部栏效果，可以在插件Footer Output输入框中贴上以下代码来实现，xxxxxxx替换成自己网站的名称。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/genesis-simple-edits-custom-footer-html-code-1024x210.jpg" alt="Genesis Simple Edits自定义底部栏HTML代码"></a></p>
                  <p>Genesis Simple Edits自定义底部栏HTML代码</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-genesis-super-customizer-300x117.jpg" alt="Genesis Super Customizer插件"></a></p>
                  <p>Genesis Super Customizer插件</p>
                  <p>使用Genesis Framework主题之后发现评论的字体偏大了，通过<a href="https://wordpress.org/plugins/genesis-super-customizer/" target="_blank" rel="noopener">Super Customizer插件</a>可以修改文章和评论默认的字体大小，还有主题的各种自定义样式修改。 另：如果是做境外网站，文章和评论的字体一般选Georgia Font。Genesis主题的分享插件推荐<a href="https://wordpress.org/plugins/genesis-simple-share/" target="_blank" rel="noopener">Genesis Simple Share</a>。</p>
                  <h3 id="8、Really-Simple-SSL"><a href="#8、Really-Simple-SSL" class="headerlink" title="8、Really Simple SSL"></a>8、<a href="https://wordpress.org/plugins/really-simple-ssl/" target="_blank" rel="noopener">Really Simple SSL</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-really-simple-ssl-300x118.jpg" alt="Really Simple SSL插件"></a></p>
                  <p>Really Simple SSL插件</p>
                  <p>网站从HTTP切换成HTTPS必备的插件，切换的过程中涉及到301重定向，URL参数的修改等等，如果手动修改难免出错，这个在<a href="https://www.chenwenguan.com/aliyun-wordpress-hosting-ssl" target="_blank" rel="noopener">阿里云免费SSL证书申请和WordPress服务器配置</a> 文章中已经有提及过。</p>
                  <h3 id="9、WP-Mail-SMTP和WPForms-Lite"><a href="#9、WP-Mail-SMTP和WPForms-Lite" class="headerlink" title="9、WP Mail SMTP和WPForms Lite"></a>9、<a href="https://wordpress.org/plugins/wp-mail-smtp/" target="_blank" rel="noopener">WP Mail SMTP</a>和<a href="https://wordpress.org/plugins/wpforms-lite/" target="_blank" rel="noopener">WPForms Lite</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-wp-mail-smtp-by-wpforms-300x117.jpg" alt="WP Mail SMTP by WPForms插件"></a></p>
                  <p>WP Mail SMTP by WPForms插件</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-contact-form-by-wpforms-300x136.jpg" alt="WPForms Lite插件"></a></p>
                  <p>WPForms Lite插件</p>
                  <p>博客中的联系页面功能可以使用上面两个插件配合实现。 WPForms Lite插件用来自定义联系人表格、注册表格等，WP Mail SMTP 插件用来设置接收邮件的参数，比如设置QQ邮箱作为博客的接收邮箱，可以参考这篇文章：<a href="http://help.websoft9.com/wordpress-practise/paas/wp/smtp.html" target="_blank" rel="noopener">WordPress如何发送邮件？</a></p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-blog-contact-form-page-1024x611.jpg" alt="Wordpress博客联系表格页面"></a></p>
                  <p>WordPress博客联系表格页面</p>
                  <p>如果需要弹窗邮件注册功能可以使用<a href="https://wordpress.org/plugins/optinmonster/" target="_blank" rel="noopener">OptinMonster</a>插件。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-popups-by-optinmonster-300x123.jpg" alt="Popups by OptinMonster插件"></a></p>
                  <p>Popups by OptinMonster插件</p>
                  <h3 id="10、Akismet"><a href="#10、Akismet" class="headerlink" title="10、Akismet"></a>10、<a href="https://wordpress.org/plugins/akismet/" target="_blank" rel="noopener">Akismet</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-akismet-anti-spam-300x129.jpg" alt="Akismet Anti-Spam插件"></a></p>
                  <p>Akismet Anti-Spam插件</p>
                  <p>垃圾评论拦截插件，避免恶意的评论。</p>
                  <h3 id="11、Easyazon"><a href="#11、Easyazon" class="headerlink" title="11、Easyazon"></a>11、<a href="https://wordpress.org/plugins/easyazon/" target="_blank" rel="noopener">Easyazon</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-easyazon-300x124.jpg" alt="EasyAzon 插件"></a></p>
                  <p>EasyAzon 插件</p>
                  <p>做境外Affiliate必备插件，EasyaAzon一个很重要的功能是可以根据地理位置设置不同的跳转链接，比如在澳大利亚可以跳转到澳大利亚区域的亚马逊店铺，加拿大可以跳转到加拿大区域的亚马逊店铺。比如网站默认的地理位置设置在美国，如果加拿大或者澳大利亚区域的用户点击链接跳转到的是美国区域的店铺，很容易就放弃购买，这样会损失一部分转化率。 10Beasts的博主曾使用geniuslink (<a href="https://www.geni.us/" target="_blank" rel="noopener">https://www.geni.us/</a>)来本地化购买链接，后面出现一个Bug，导致返利成了geniuslink的收益，后面他就弃用了。</p>
                  <p>EasyAzon设置对应区域的链接地址</p>
                  <h3 id="12、TinyMCE-Advanced"><a href="#12、TinyMCE-Advanced" class="headerlink" title="12、TinyMCE Advanced"></a>12、<a href="https://wordpress.org/plugins/tinymce-advanced/" target="_blank" rel="noopener">TinyMCE Advanced</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-tinymce-advanced-300x119.jpg" alt="TinyMCE Advanced插件"></a></p>
                  <p>TinyMCE Advanced插件</p>
                  <p>编辑插件推荐在WordPress自带的编辑工具上，安装TinyMCE Advanced插件，WordPress自带的插件功能太简陋了，覆盖安装第三方插件多多少少会有点问题。TinyMCE Advanced插件可以提供字体大小设置功能，可以在插件设置页面拖动在编辑栏上要显示的功能模块。 一定不要安装Kindeditor For WordPress插件，这个插件很久没更新了，而且是覆盖安装，最大的一个问题是在上下滑动编辑区域的时候无法悬浮编辑工具栏，这样要编辑设置文本属性的时候每次都要滑动到文章头部设置，非常麻烦。而且插入媒体内容的时候每次都是把内容加到文章末尾，不是在输入光标的位置插入内容，每次都要从文章末尾剪切黏贴到插入的位置。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-kindeditor-for-wordpress-300x117.jpg" alt="Kindeditor For WordPress插件"></a></p>
                  <p>Kindeditor For WordPress插件</p>
                  <h3 id="13、Rel-Nofollow-Checkbox"><a href="#13、Rel-Nofollow-Checkbox" class="headerlink" title="13、Rel Nofollow Checkbox"></a>13、<a href="https://wordpress.org/plugins/rel-nofollow-checkbox/" target="_blank" rel="noopener">Rel Nofollow Checkbox</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-rel-nofollow-checkbox-300x119.jpg" alt="Rel Nofollow Checkbox插件"></a></p>
                  <p>Rel Nofollow Checkbox插件</p>
                  <p>文章中的出站链接一般都要设置成nofollow属性，避免权重传递。其他的Nofollow插件会有这样那样的问题，试了几个插件只有这个没有Bug。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-rel-nofollow-checkbox-setting-300x300.jpg" alt="文章链接nofollow属性设置"></a></p>
                  <p>文章链接nofollow属性设置</p>
                  <h3 id="14、Google-Analytics-for-WordPress-by-MonsterInsights"><a href="#14、Google-Analytics-for-WordPress-by-MonsterInsights" class="headerlink" title="14、Google Analytics for WordPress by MonsterInsights"></a>14、<a href="https://wordpress.org/plugins/google-analytics-for-wordpress/" target="_blank" rel="noopener">Google Analytics for WordPress by MonsterInsights</a></h3>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-google-analytics-dashboard-plugin-for-wordpress-by-monsterinfights-300x138.jpg" alt="Google Analytics Dashboard Plugin for WordPress by MonsterInsights插件"></a></p>
                  <p>Google Analytics Dashboard Plugin for WordPress by MonsterInsights插件</p>
                  <p>为了避免每次单独登陆Google Analytics的麻烦，可以集成Google Analytics到WordPress操作面板中，但似乎在阿里云服务器上的防火墙会屏蔽Google Analytics链接的访问，境外网站的使用没问题。 这边不建议使用WP Statistics插件，虽然用这个插件可以看到很多详细的访问数据，但在集成之后发现网站页面的加载速度明显变长了，而且会产生大量的Log记录。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/02/wordpress-plugin-wp-statistics-300x117.jpg" alt="WP Statistics插件"></a></p>
                  <p>WP Statistics插件</p>
                  <p>15、<a href="https://wordpress.org/plugins/wp-downgrade/" target="_blank" rel="noopener">WP Downgrade</a></p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/03/wordpress-plugin-wp-downgrade-300x131.jpg" alt="WordPress WP Downgrade插件"></a></p>
                  <p>WordPress WP Downgrade插件</p>
                  <p>如果WordPress版本被服务器自动升级到5.0，之后就容易出现很多WordPress插件不兼容的情况，这就需要对WordPress版本进行降级，使用这个插件输入要降级或升级的版本号操作即可。使用完之后可以关闭此插件，需要的时候再激活。 </p>
                  <h3 id="TIPS"><a href="#TIPS" class="headerlink" title="TIPS:"></a>TIPS:</h3>
                  <h4 id="1、怎么下载旧版本WordPress插件？"><a href="#1、怎么下载旧版本WordPress插件？" class="headerlink" title="1、怎么下载旧版本WordPress插件？"></a>1、怎么下载旧版本WordPress插件？</h4>
                  <p>有的时候会出现升级WordPress版本或者插件自更新到高版本出现插件不兼容的异常，这个时候就需要回退安装旧版本的插件。首先进入WordPress网站主页<a href="https://wordpress.org/" target="_blank" rel="noopener">https://wordpress.org/</a>，比如要下载Jetpack旧版本插件，先进入Jetpack插件主页，在插件页面右侧有一个Advanced View入口。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/04/wordpress-plugin-advanced-view-1024x483.jpg" alt="插件历史版本下载入口"></a></p>
                  <p>插件历史版本下载入口</p>
                  <p>点击进入Advanced View页面，在PREVIOUS VERSIONS模块就可以选择历史插件版本，之后进行下载。</p>
                  <p><a href="https://www.chenwenguan.com/best-wordpress-plugins-this-year/" target="_blank" rel="noopener"><img src="https://www.chenwenguan.com/wp-content/uploads/2019/04/wordpress-plugin-previous-versions-download-1024x486.jpg" alt="插件历史版本下载"></a></p>
                  <p>插件历史版本下载</p>
                  </p>
                </div>
              </div>
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-user"></i>
                  </span>
                  <span class="post-meta-item-text">作者</span>
                  <span><a href="/authors/崔庆才" class="author" itemprop="url" rel="index">崔庆才</a></span>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>
                  <time title="创建时间：2019-07-02 11:04:40" itemprop="dateCreated datePublished" datetime="2019-07-02T11:04:40+08:00">2019-07-02</time>
                </span>
                <span id="/6759.html" class="post-meta-item leancloud_visitors" data-flag-title="2019 WordPress 必备插件推荐" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="fa fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
                <span class="post-meta-item" title="本文字数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-file-word"></i>
                  </span>
                  <span class="post-meta-item-text">本文字数：</span>
                  <span>3.7k</span>
                </span>
                <span class="post-meta-item" title="阅读时长">
                  <span class="post-meta-item-icon">
                    <i class="far fa-clock"></i>
                  </span>
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                  <span>3 分钟</span>
                </span>
              </div>
            </article>
            <script>
              document.querySelectorAll('.random').forEach(item => item.src = "https://picsum.photos/id/" + Math.floor(Math.random() * Math.floor(300)) + "/200/133")

            </script>
            <nav class="pagination">
              <a class="extend prev" rel="prev" href="/page/12/"><i class="fa fa-angle-left" aria-label="上一页"></i></a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/12/">12</a><span class="page-number current">13</span><a class="page-number" href="/page/14/">14</a><span class="space">&hellip;</span><a class="page-number" href="/page/31/">31</a><a class="extend next" rel="next" href="/page/14/"><i class="fa fa-angle-right" aria-label="下一页"></i></a>
            </nav>
          </div>
          <script>
            window.addEventListener('tabs:register', () =>
            {
              let
              {
                activeClass
              } = CONFIG.comments;
              if (CONFIG.comments.storage)
              {
                activeClass = localStorage.getItem('comments_active') || activeClass;
              }
              if (activeClass)
              {
                let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
                if (activeTab)
                {
                  activeTab.click();
                }
              }
            });
            if (CONFIG.comments.storage)
            {
              window.addEventListener('tabs:click', event =>
              {
                if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
                let commentClass = event.target.classList[1];
                localStorage.setItem('comments_active', commentClass);
              });
            }

          </script>
        </div>
        <div class="toggle sidebar-toggle">
          <span class="toggle-line toggle-line-first"></span>
          <span class="toggle-line toggle-line-middle"></span>
          <span class="toggle-line toggle-line-last"></span>
        </div>
        <aside class="sidebar">
          <div class="sidebar-inner">
            <ul class="sidebar-nav motion-element">
              <li class="sidebar-nav-toc"> 文章目录 </li>
              <li class="sidebar-nav-overview"> 站点概览 </li>
            </ul>
            <!--noindex-->
            <div class="post-toc-wrap sidebar-panel">
            </div>
            <!--/noindex-->
            <div class="site-overview-wrap sidebar-panel">
              <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
                <img class="site-author-image" itemprop="image" alt="崔庆才" src="/images/avatar.png">
                <p class="site-author-name" itemprop="name">崔庆才</p>
                <div class="site-description" itemprop="description">崔庆才的个人站点，记录生活的瞬间，分享学习的心得。</div>
              </div>
              <div class="site-state-wrap motion-element">
                <nav class="site-state">
                  <div class="site-state-item site-state-posts">
                    <a href="/archives/">
                      <span class="site-state-item-count">608</span>
                      <span class="site-state-item-name">日志</span>
                    </a>
                  </div>
                  <div class="site-state-item site-state-categories">
                    <a href="/categories/">
                      <span class="site-state-item-count">24</span>
                      <span class="site-state-item-name">分类</span></a>
                  </div>
                  <div class="site-state-item site-state-tags">
                    <a href="/tags/">
                      <span class="site-state-item-count">156</span>
                      <span class="site-state-item-name">标签</span></a>
                  </div>
                </nav>
              </div>
              <div class="links-of-author motion-element">
                <span class="links-of-author-item">
                  <a href="https://github.com/Germey" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;Germey" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
                </span>
                <span class="links-of-author-item">
                  <a href="mailto:cqc@cuiqingcai.com.com" title="邮件 → mailto:cqc@cuiqingcai.com.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>邮件</a>
                </span>
                <span class="links-of-author-item">
                  <a href="https://weibo.com/cuiqingcai" title="微博 → https:&#x2F;&#x2F;weibo.com&#x2F;cuiqingcai" rel="noopener" target="_blank"><i class="fab fa-weibo fa-fw"></i>微博</a>
                </span>
                <span class="links-of-author-item">
                  <a href="https://www.zhihu.com/people/Germey" title="知乎 → https:&#x2F;&#x2F;www.zhihu.com&#x2F;people&#x2F;Germey" rel="noopener" target="_blank"><i class="fa fa-magic fa-fw"></i>知乎</a>
                </span>
              </div>
            </div>
            <div style=" width: 100%;" class="sidebar-panel sidebar-panel-image sidebar-panel-active">
              <a href="https://tutorial.lengyue.video/?coupon=12ef4b1a-a3db-11ea-bb37-0242ac130002_cqx_850" target="_blank" rel="noopener">
                <img src="https://qiniu.cuiqingcai.com/bco2a.png" style=" width: 100%;">
              </a>
            </div>
            <div style=" width: 100%;" class="sidebar-panel sidebar-panel-image sidebar-panel-active">
              <a href="http://www.ipidea.net/?utm-source=cqc&utm-keyword=?cqc" target="_blank" rel="noopener">
                <img src="https://qiniu.cuiqingcai.com/0ywun.png" style=" width: 100%;">
              </a>
            </div>
            <div class="sidebar-panel sidebar-panel-tags sidebar-panel-active">
              <h4 class="name"> 标签云 </h4>
              <div class="content">
                <a href="/tags/2048/" style="font-size: 10px;">2048</a> <a href="/tags/API/" style="font-size: 10px;">API</a> <a href="/tags/Bootstrap/" style="font-size: 11.25px;">Bootstrap</a> <a href="/tags/CDN/" style="font-size: 10px;">CDN</a> <a href="/tags/CQC/" style="font-size: 10px;">CQC</a> <a href="/tags/CSS/" style="font-size: 10px;">CSS</a> <a href="/tags/CSS-%E5%8F%8D%E7%88%AC%E8%99%AB/" style="font-size: 10px;">CSS 反爬虫</a> <a href="/tags/CV/" style="font-size: 10px;">CV</a> <a href="/tags/Django/" style="font-size: 10px;">Django</a> <a href="/tags/Eclipse/" style="font-size: 11.25px;">Eclipse</a> <a href="/tags/FTP/" style="font-size: 10px;">FTP</a> <a href="/tags/Git/" style="font-size: 10px;">Git</a> <a href="/tags/GitHub/" style="font-size: 13.75px;">GitHub</a> <a href="/tags/HTML5/" style="font-size: 10px;">HTML5</a> <a href="/tags/Hexo/" style="font-size: 10px;">Hexo</a> <a href="/tags/IT/" style="font-size: 10px;">IT</a> <a href="/tags/JSP/" style="font-size: 10px;">JSP</a> <a href="/tags/JavaScript/" style="font-size: 10px;">JavaScript</a> <a href="/tags/K8s/" style="font-size: 10px;">K8s</a> <a href="/tags/LOGO/" style="font-size: 10px;">LOGO</a> <a href="/tags/Linux/" style="font-size: 10px;">Linux</a> <a href="/tags/MIUI/" style="font-size: 10px;">MIUI</a> <a href="/tags/MongoDB/" style="font-size: 10px;">MongoDB</a> <a href="/tags/Mysql/" style="font-size: 10px;">Mysql</a> <a href="/tags/NBA/" style="font-size: 10px;">NBA</a> <a href="/tags/PHP/" style="font-size: 11.25px;">PHP</a> <a href="/tags/PS/" style="font-size: 10px;">PS</a> <a href="/tags/Pathlib/" style="font-size: 10px;">Pathlib</a> <a href="/tags/PhantomJS/" style="font-size: 10px;">PhantomJS</a> <a href="/tags/Python/" style="font-size: 15px;">Python</a> <a href="/tags/Python3/" style="font-size: 12.5px;">Python3</a> <a href="/tags/Pythonic/" style="font-size: 10px;">Pythonic</a> <a href="/tags/QQ/" style="font-size: 10px;">QQ</a> <a href="/tags/Redis/" style="font-size: 10px;">Redis</a> <a href="/tags/SAE/" style="font-size: 10px;">SAE</a> <a href="/tags/SSH/" style="font-size: 10px;">SSH</a> <a href="/tags/SVG/" style="font-size: 10px;">SVG</a> <a href="/tags/Scrapy/" style="font-size: 10px;">Scrapy</a> <a href="/tags/Scrapy-redis/" style="font-size: 10px;">Scrapy-redis</a> <a href="/tags/Scrapy%E5%88%86%E5%B8%83%E5%BC%8F/" style="font-size: 10px;">Scrapy分布式</a> <a href="/tags/Selenium/" style="font-size: 10px;">Selenium</a> <a href="/tags/TKE/" style="font-size: 10px;">TKE</a> <a href="/tags/Ubuntu/" style="font-size: 11.25px;">Ubuntu</a> <a href="/tags/VS-Code/" style="font-size: 10px;">VS Code</a> <a href="/tags/Vs-Code/" style="font-size: 10px;">Vs Code</a> <a href="/tags/Vue/" style="font-size: 11.25px;">Vue</a> <a href="/tags/Webpack/" style="font-size: 10px;">Webpack</a> <a href="/tags/Windows/" style="font-size: 10px;">Windows</a> <a href="/tags/Winpcap/" style="font-size: 10px;">Winpcap</a> <a href="/tags/WordPress/" style="font-size: 13.75px;">WordPress</a> <a href="/tags/Youtube/" style="font-size: 11.25px;">Youtube</a> <a href="/tags/android/" style="font-size: 10px;">android</a> <a href="/tags/ansible/" style="font-size: 10px;">ansible</a> <a href="/tags/cocos2d-x/" style="font-size: 10px;">cocos2d-x</a> <a href="/tags/e6/" style="font-size: 10px;">e6</a> <a href="/tags/fitvids/" style="font-size: 10px;">fitvids</a> <a href="/tags/git/" style="font-size: 11.25px;">git</a> <a href="/tags/json/" style="font-size: 10px;">json</a> <a href="/tags/js%E9%80%86%E5%90%91/" style="font-size: 10px;">js逆向</a> <a href="/tags/kubernetes/" style="font-size: 10px;">kubernetes</a> <a href="/tags/log/" style="font-size: 10px;">log</a> <a href="/tags/logging/" style="font-size: 10px;">logging</a> <a href="/tags/matlab/" style="font-size: 11.25px;">matlab</a> <a href="/tags/python/" style="font-size: 20px;">python</a> <a href="/tags/pytube/" style="font-size: 11.25px;">pytube</a> <a href="/tags/pywin32/" style="font-size: 10px;">pywin32</a> <a href="/tags/style/" style="font-size: 10px;">style</a> <a href="/tags/tomcat/" style="font-size: 10px;">tomcat</a> <a href="/tags/ubuntu/" style="font-size: 10px;">ubuntu</a> <a href="/tags/uwsgi/" style="font-size: 10px;">uwsgi</a> <a href="/tags/vsftpd/" style="font-size: 10px;">vsftpd</a> <a href="/tags/wamp/" style="font-size: 10px;">wamp</a> <a href="/tags/wineQQ/" style="font-size: 10px;">wineQQ</a> <a href="/tags/%E4%B8%83%E7%89%9B/" style="font-size: 11.25px;">七牛</a> <a href="/tags/%E4%B8%8A%E6%B5%B7/" style="font-size: 10px;">上海</a> <a href="/tags/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/" style="font-size: 10px;">个人网站</a> <a href="/tags/%E4%B8%BB%E9%A2%98/" style="font-size: 10px;">主题</a> <a href="/tags/%E4%BA%91%E4%BA%A7%E5%93%81/" style="font-size: 10px;">云产品</a> <a href="/tags/%E4%BA%91%E5%AD%98%E5%82%A8/" style="font-size: 10px;">云存储</a> <a href="/tags/%E4%BA%AC%E4%B8%9C%E4%BA%91/" style="font-size: 10px;">京东云</a> <a href="/tags/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/" style="font-size: 12.5px;">人工智能</a> <a href="/tags/%E4%BB%A3%E7%90%86/" style="font-size: 10px;">代理</a> <a href="/tags/%E4%BB%A3%E7%A0%81/" style="font-size: 10px;">代码</a> <a href="/tags/%E4%BB%A3%E7%A0%81%E5%88%86%E4%BA%AB%E5%9B%BE/" style="font-size: 10px;">代码分享图</a> <a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size: 10px;">优化</a> <a href="/tags/%E4%BD%8D%E8%BF%90%E7%AE%97/" style="font-size: 10px;">位运算</a> <a href="/tags/%E5%85%AC%E4%BC%97%E5%8F%B7/" style="font-size: 10px;">公众号</a> <a href="/tags/%E5%88%86%E4%BA%AB/" style="font-size: 10px;">分享</a> <a href="/tags/%E5%88%86%E5%B8%83%E5%BC%8F/" style="font-size: 10px;">分布式</a> <a href="/tags/%E5%88%9B%E4%B8%9A/" style="font-size: 10px;">创业</a> <a href="/tags/%E5%89%8D%E7%AB%AF/" style="font-size: 12.5px;">前端</a> <a href="/tags/%E5%8D%9A%E5%AE%A2/" style="font-size: 10px;">博客</a> <a href="/tags/%E5%8E%9F%E7%94%9FAPP/" style="font-size: 10px;">原生APP</a> <a href="/tags/%E5%8F%8D%E7%88%AC%E8%99%AB/" style="font-size: 12.5px;">反爬虫</a> <a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size: 10px;">命令</a> <a href="/tags/%E5%93%8D%E5%BA%94%E5%BC%8F%E5%B8%83%E5%B1%80/" style="font-size: 10px;">响应式布局</a> <a href="/tags/%E5%9E%83%E5%9C%BE%E9%82%AE%E4%BB%B6/" style="font-size: 10px;">垃圾邮件</a> <a href="/tags/%E5%9F%9F%E5%90%8D%E7%BB%91%E5%AE%9A/" style="font-size: 10px;">域名绑定</a> <a href="/tags/%E5%A4%8D%E7%9B%98/" style="font-size: 10px;">复盘</a> <a href="/tags/%E5%A4%A7%E4%BC%97%E7%82%B9%E8%AF%84/" style="font-size: 10px;">大众点评</a> <a href="/tags/%E5%AD%97%E4%BD%93%E5%8F%8D%E7%88%AC%E8%99%AB/" style="font-size: 10px;">字体反爬虫</a> <a href="/tags/%E5%AD%97%E7%AC%A6%E9%97%AE%E9%A2%98/" style="font-size: 10px;">字符问题</a> <a href="/tags/%E5%AD%A6%E4%B9%A0%E6%96%B9%E6%B3%95/" style="font-size: 10px;">学习方法</a> <a href="/tags/%E5%AE%89%E5%8D%93/" style="font-size: 10px;">安卓</a> <a href="/tags/%E5%AE%9E%E7%94%A8/" style="font-size: 10px;">实用</a> <a href="/tags/%E5%B0%81%E9%9D%A2/" style="font-size: 10px;">封面</a> <a href="/tags/%E5%B4%94%E5%BA%86%E6%89%8D/" style="font-size: 18.75px;">崔庆才</a> <a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size: 12.5px;">工具</a> <a href="/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/" style="font-size: 10px;">开发工具</a> <a href="/tags/%E5%BE%AE%E8%BD%AF/" style="font-size: 10px;">微软</a> <a href="/tags/%E6%80%9D%E8%80%83/" style="font-size: 10px;">思考</a> <a href="/tags/%E6%89%8B%E6%9C%BA%E8%AE%BF%E9%97%AE/" style="font-size: 10px;">手机访问</a> <a href="/tags/%E6%95%99%E7%A8%8B/" style="font-size: 10px;">教程</a> <a href="/tags/%E6%95%99%E8%82%B2/" style="font-size: 10px;">教育</a> <a href="/tags/%E6%96%B0%E4%B9%A6/" style="font-size: 12.5px;">新书</a> <a href="/tags/%E6%96%B9%E6%B3%95%E8%AE%BA/" style="font-size: 10px;">方法论</a> <a href="/tags/%E6%97%85%E6%B8%B8/" style="font-size: 10px;">旅游</a> <a href="/tags/%E6%97%A5%E5%BF%97/" style="font-size: 10px;">日志</a> <a href="/tags/%E6%9A%97%E6%97%B6%E9%97%B4/" style="font-size: 10px;">暗时间</a> <a href="/tags/%E6%9D%9C%E5%85%B0%E7%89%B9/" style="font-size: 11.25px;">杜兰特</a> <a href="/tags/%E6%A1%8C%E9%9D%A2/" style="font-size: 10px;">桌面</a> <a href="/tags/%E6%AD%8C%E5%8D%95/" style="font-size: 10px;">歌单</a> <a href="/tags/%E6%B1%9F%E5%8D%97/" style="font-size: 10px;">江南</a> <a href="/tags/%E6%B8%B8%E6%88%8F/" style="font-size: 10px;">游戏</a> <a href="/tags/%E7%84%A6%E8%99%91/" style="font-size: 10px;">焦虑</a> <a href="/tags/%E7%88%AC%E8%99%AB/" style="font-size: 16.25px;">爬虫</a> <a href="/tags/%E7%88%AC%E8%99%AB%E4%B9%A6%E7%B1%8D/" style="font-size: 11.25px;">爬虫书籍</a> <a href="/tags/%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/" style="font-size: 10px;">环境变量</a> <a href="/tags/%E7%94%9F%E6%B4%BB%E7%AC%94%E8%AE%B0/" style="font-size: 10px;">生活笔记</a> <a href="/tags/%E7%99%BB%E5%BD%95/" style="font-size: 10px;">登录</a> <a href="/tags/%E7%9F%A5%E4%B9%8E/" style="font-size: 10px;">知乎</a> <a href="/tags/%E7%9F%AD%E4%BF%A1/" style="font-size: 10px;">短信</a> <a href="/tags/%E7%9F%AD%E4%BF%A1%E9%AA%8C%E8%AF%81%E7%A0%81/" style="font-size: 10px;">短信验证码</a> <a href="/tags/%E7%AC%94%E8%AE%B0%E8%BD%AF%E4%BB%B6/" style="font-size: 10px;">笔记软件</a> <a href="/tags/%E7%AF%AE%E7%BD%91/" style="font-size: 10px;">篮网</a> <a href="/tags/%E7%BA%B8%E5%BC%A0/" style="font-size: 10px;">纸张</a> <a href="/tags/%E7%BB%84%E4%BB%B6/" style="font-size: 10px;">组件</a> <a href="/tags/%E7%BD%91%E7%AB%99/" style="font-size: 10px;">网站</a> <a href="/tags/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/" style="font-size: 11.25px;">网络爬虫</a> <a href="/tags/%E7%BE%8E%E5%AD%A6/" style="font-size: 10px;">美学</a> <a href="/tags/%E8%82%89%E5%A4%B9%E9%A6%8D/" style="font-size: 10px;">肉夹馍</a> <a href="/tags/%E8%85%BE%E8%AE%AF%E4%BA%91/" style="font-size: 10px;">腾讯云</a> <a href="/tags/%E8%87%AA%E5%BE%8B/" style="font-size: 10px;">自律</a> <a href="/tags/%E8%A5%BF%E5%B0%91%E7%88%B7/" style="font-size: 10px;">西少爷</a> <a href="/tags/%E8%A7%86%E9%A2%91/" style="font-size: 10px;">视频</a> <a href="/tags/%E8%B0%B7%E6%AD%8C%E9%AA%8C%E8%AF%81%E7%A0%81/" style="font-size: 10px;">谷歌验证码</a> <a href="/tags/%E8%BF%90%E8%90%A5/" style="font-size: 10px;">运营</a> <a href="/tags/%E8%BF%9C%E7%A8%8B/" style="font-size: 10px;">远程</a> <a href="/tags/%E9%80%86%E5%90%91/" style="font-size: 10px;">逆向</a> <a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size: 10px;">配置</a> <a href="/tags/%E9%87%8D%E8%A3%85/" style="font-size: 10px;">重装</a> <a href="/tags/%E9%98%BF%E6%9D%9C/" style="font-size: 10px;">阿杜</a> <a href="/tags/%E9%9D%99%E8%A7%85/" style="font-size: 17.5px;">静觅</a> <a href="/tags/%E9%A2%A0%E8%A6%86/" style="font-size: 10px;">颠覆</a> <a href="/tags/%E9%A3%9E%E4%BF%A1/" style="font-size: 10px;">飞信</a> <a href="/tags/%E9%B8%BF%E8%92%99/" style="font-size: 10px;">鸿蒙</a>
              </div>
              <script>
                const tagsColors = ['#00a67c', '#5cb85c', '#d9534f', '#567e95', '#b37333', '#f4843d', '#15a287']
                const tagsElements = document.querySelectorAll('.sidebar-panel-tags .content a')
                tagsElements.forEach((item) =>
                {
                  item.style.backgroundColor = tagsColors[Math.floor(Math.random() * tagsColors.length)]
                })

              </script>
            </div>
            <div class="sidebar-panel sidebar-panel-categories sidebar-panel-active">
              <h4 class="name"> 分类 </h4>
              <div class="content">
                <ul class="category-list">
                  <li class="category-list-item"><a class="category-list-link" href="/categories/C-C/">C/C++</a><span class="category-list-count">23</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/HTML/">HTML</a><span class="category-list-count">14</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/Java/">Java</a><span class="category-list-count">5</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/JavaScript/">JavaScript</a><span class="category-list-count">26</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/Linux/">Linux</a><span class="category-list-count">15</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/Markdown/">Markdown</a><span class="category-list-count">1</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/Net/">Net</a><span class="category-list-count">4</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/Other/">Other</a><span class="category-list-count">39</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/PHP/">PHP</a><span class="category-list-count">27</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/Paper/">Paper</a><span class="category-list-count">2</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/Python/">Python</a><span class="category-list-count">261</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/TypeScript/">TypeScript</a><span class="category-list-count">2</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/%E4%B8%AA%E4%BA%BA%E5%B1%95%E7%A4%BA/">个人展示</a><span class="category-list-count">1</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/%E4%B8%AA%E4%BA%BA%E6%97%A5%E8%AE%B0/">个人日记</a><span class="category-list-count">9</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95/">个人记录</a><span class="category-list-count">4</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/%E4%B8%AA%E4%BA%BA%E9%9A%8F%E7%AC%94/">个人随笔</a><span class="category-list-count">15</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE/">安装配置</a><span class="category-list-count">59</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/">技术杂谈</a><span class="category-list-count">88</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/%E6%9C%AA%E5%88%86%E7%B1%BB/">未分类</a><span class="category-list-count">1</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/%E7%94%9F%E6%B4%BB%E7%AC%94%E8%AE%B0/">生活笔记</a><span class="category-list-count">1</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/%E7%A6%8F%E5%88%A9%E4%B8%93%E5%8C%BA/">福利专区</a><span class="category-list-count">6</span></li>
                  <li class="category-list-item"><a class="category-list-link" href="/categories/%E8%81%8C%E4%BD%8D%E6%8E%A8%E8%8D%90/">职位推荐</a><span class="category-list-count">2</span></li>
                </ul>
              </div>
            </div>
            <div class="sidebar-panel sidebar-panel-friends sidebar-panel-active">
              <h4 class="name"> 友情链接 </h4>
              <ul class="friends">
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/j2dub.jpg">
                  </span>
                  <span class="link">
                    <a href="https://www.findhao.net/" target="_blank" rel="noopener">FindHao</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/ou6mm.jpg">
                  </span>
                  <span class="link">
                    <a href="https://diygod.me/" target="_blank" rel="noopener">DIYgod</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/6apxu.jpg">
                  </span>
                  <span class="link">
                    <a href="https://www.51dev.com/" target="_blank" rel="noopener">IT技术社区</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://www.jankl.com/img/titleshu.jpg">
                  </span>
                  <span class="link">
                    <a href="https://www.jankl.com/" target="_blank" rel="noopener">liberalist</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/bqlbs.png">
                  </span>
                  <span class="link">
                    <a href="http://www.urselect.com/" target="_blank" rel="noopener">优社电商</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/8s88c.jpg">
                  </span>
                  <span class="link">
                    <a href="https://www.yuanrenxue.com/" target="_blank" rel="noopener">猿人学</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/2wgg5.jpg">
                  </span>
                  <span class="link">
                    <a href="https://www.yunlifang.cn/" target="_blank" rel="noopener">云立方</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/shwr6.png">
                  </span>
                  <span class="link">
                    <a href="http://lanbing510.info/" target="_blank" rel="noopener">冰蓝</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/blvoh.jpg">
                  </span>
                  <span class="link">
                    <a href="https://lengyue.me/" target="_blank" rel="noopener">冷月</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="http://qianxunclub.com/favicon.png">
                  </span>
                  <span class="link">
                    <a href="http://qianxunclub.com/" target="_blank" rel="noopener">千寻啊千寻</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/0044u.jpg">
                  </span>
                  <span class="link">
                    <a href="http://kodcloud.com/" target="_blank" rel="noopener">可道云</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/ygnpn.jpg">
                  </span>
                  <span class="link">
                    <a href="http://www.kunkundashen.cn/" target="_blank" rel="noopener">坤坤大神</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/22uv1.png">
                  </span>
                  <span class="link">
                    <a href="http://www.cenchong.com/" target="_blank" rel="noopener">岑冲博客</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/ev9kl.png">
                  </span>
                  <span class="link">
                    <a href="http://www.zxiaoji.com/" target="_blank" rel="noopener">张小鸡</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://www.503error.com/favicon.ico">
                  </span>
                  <span class="link">
                    <a href="https://www.503error.com/" target="_blank" rel="noopener">张志明个人博客</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/x714o.jpg">
                  </span>
                  <span class="link">
                    <a href="http://www.hubwiz.com/" target="_blank" rel="noopener">汇智网</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/129d8.png">
                  </span>
                  <span class="link">
                    <a href="https://www.bysocket.com/" target="_blank" rel="noopener">泥瓦匠BYSocket</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://www.xiongge.club/favicon.ico">
                  </span>
                  <span class="link">
                    <a href="https://www.xiongge.club/" target="_blank" rel="noopener">熊哥club</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/3w4fe.png">
                  </span>
                  <span class="link">
                    <a href="https://zerlong.com/" target="_blank" rel="noopener">知语</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/44hxf.png">
                  </span>
                  <span class="link">
                    <a href="http://redstonewill.com/" target="_blank" rel="noopener">红色石头</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/8g1fk.jpg">
                  </span>
                  <span class="link">
                    <a href="http://www.laodong.me/" target="_blank" rel="noopener">老董博客</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/wkaus.jpg">
                  </span>
                  <span class="link">
                    <a href="https://zhaoshuai.me/" target="_blank" rel="noopener">碎念</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/pgo0r.jpg">
                  </span>
                  <span class="link">
                    <a href="https://www.chenwenguan.com/" target="_blank" rel="noopener">陈文管的博客</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/kk82a.jpg">
                  </span>
                  <span class="link">
                    <a href="https://www.lxlinux.net/" target="_blank" rel="noopener">良许Linux教程网</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/lj0t2.jpg">
                  </span>
                  <span class="link">
                    <a href="https://tanqingbo.cn/" target="_blank" rel="noopener">IT码农</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/i8cdr.png">
                  </span>
                  <span class="link">
                    <a href="https://junyiseo.com/" target="_blank" rel="noopener">均益个人博客</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/chwv2.png">
                  </span>
                  <span class="link">
                    <a href="https://brucedone.com/" target="_blank" rel="noopener">大鱼的鱼塘</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/2y43o.png">
                  </span>
                  <span class="link">
                    <a href="http://bbs.nightteam.cn/" target="_blank" rel="noopener">夜幕爬虫安全论坛</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/zvc3w.jpg">
                  </span>
                  <span class="link">
                    <a href="https://www.weishidong.com/" target="_blank" rel="noopener">韦世东的技术专栏</a>
                  </span>
                </li>
                <li class="friend">
                  <span class="logo">
                    <img src="https://qiniu.cuiqingcai.com/ebudy.jpg">
                  </span>
                  <span class="link">
                    <a href="https://chuanjiabing.com/" target="_blank" rel="noopener">穿甲兵技术社区</a>
                  </span>
                </li>
              </ul>
            </div>
          </div>
        </aside>
        <div id="sidebar-dimmer"></div>
      </div>
    </main>
    <footer class="footer">
      <div class="footer-inner">
        <div class="copyright"> &copy; <span itemprop="copyrightYear">2021</span>
          <span class="with-love">
            <i class="fa fa-heart"></i>
          </span>
          <span class="author" itemprop="copyrightHolder">崔庆才丨静觅</span>
          <span class="post-meta-divider">|</span>
          <span class="post-meta-item-icon">
            <i class="fa fa-chart-area"></i>
          </span>
          <span title="站点总字数">2.6m</span>
          <span class="post-meta-divider">|</span>
          <span class="post-meta-item-icon">
            <i class="fa fa-coffee"></i>
          </span>
          <span title="站点阅读时长">39:54</span>
        </div>
        <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://pisces.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Pisces</a> 强力驱动 </div>
        <div class="beian"><a href="https://beian.miit.gov.cn/" rel="noopener" target="_blank">京ICP备18015597号-1 </a>
        </div>
        <script>
          (function ()
          {
            function leancloudSelector(url)
            {
              url = encodeURI(url);
              return document.getElementById(url).querySelector('.leancloud-visitors-count');
            }

            function addCount(Counter)
            {
              var visitors = document.querySelector('.leancloud_visitors');
              var url = decodeURI(visitors.id);
              var title = visitors.dataset.flagTitle;
              Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify(
              {
                url
              }))).then(response => response.json()).then((
              {
                results
              }) =>
              {
                if (results.length > 0)
                {
                  var counter = results[0];
                  leancloudSelector(url).innerText = counter.time + 1;
                  Counter('put', '/classes/Counter/' + counter.objectId,
                  {
                    time:
                    {
                      '__op': 'Increment',
                      'amount': 1
                    }
                  }).catch(error =>
                  {
                    console.error('Failed to save visitor count', error);
                  });
                }
                else
                {
                  Counter('post', '/classes/Counter',
                  {
                    title,
                    url,
                    time: 1
                  }).then(response => response.json()).then(() =>
                  {
                    leancloudSelector(url).innerText = 1;
                  }).catch(error =>
                  {
                    console.error('Failed to create', error);
                  });
                }
              }).catch(error =>
              {
                console.error('LeanCloud Counter Error', error);
              });
            }

            function showTime(Counter)
            {
              var visitors = document.querySelectorAll('.leancloud_visitors');
              var entries = [...visitors].map(element =>
              {
                return decodeURI(element.id);
              });
              Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify(
              {
                url:
                {
                  '$in': entries
                }
              }))).then(response => response.json()).then((
              {
                results
              }) =>
              {
                for (let url of entries)
                {
                  let target = results.find(item => item.url === url);
                  leancloudSelector(url).innerText = target ? target.time : 0;
                }
              }).catch(error =>
              {
                console.error('LeanCloud Counter Error', error);
              });
            }
            let
            {
              app_id,
              app_key,
              server_url
            } = {
              "enable": true,
              "app_id": "6X5dRQ0pnPWJgYy8SXOg0uID-gzGzoHsz",
              "app_key": "ziLDVEy73ne5HtFTiGstzHMS",
              "server_url": "https://6x5drq0p.lc-cn-n1-shared.com",
              "security": false
            };

            function fetchData(api_server)
            {
              var Counter = (method, url, data) =>
              {
                return fetch(`${api_server}/1.1${url}`,
                {
                  method,
                  headers:
                  {
                    'X-LC-Id': app_id,
                    'X-LC-Key': app_key,
                    'Content-Type': 'application/json',
                  },
                  body: JSON.stringify(data)
                });
              };
              if (CONFIG.page.isPost)
              {
                if (CONFIG.hostname !== location.hostname) return;
                addCount(Counter);
              }
              else if (document.querySelectorAll('.post-title-link').length >= 1)
              {
                showTime(Counter);
              }
            }
            let api_server = app_id.slice(-9) !== '-MdYXbMMI' ? server_url : `https://${app_id.slice(0, 8).toLowerCase()}.api.lncldglobal.com`;
            if (api_server)
            {
              fetchData(api_server);
            }
            else
            {
              fetch('https://app-router.leancloud.cn/2/route?appId=' + app_id).then(response => response.json()).then((
              {
                api_server
              }) =>
              {
                fetchData('https://' + api_server);
              });
            }
          })();

        </script>
      </div>
      <div class="footer-stat">
        <span id="cnzz_stat_icon_1279355174"></span>
        <script type="text/javascript">
          document.write(unescape("%3Cspan id='cnzz_stat_icon_1279355174'%3E%3C/span%3E%3Cscript src='https://v1.cnzz.com/z_stat.php%3Fid%3D1279355174%26online%3D1%26show%3Dline' type='text/javascript'%3E%3C/script%3E"));

        </script>
      </div>
    </footer>
  </div>
  <script src="//cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/pangu@4/dist/browser/pangu.min.js"></script>
  <script src="/js/utils.js"></script>
  <script src="/.js"></script>
  <script src="/js/schemes/pisces.js"></script>
  <script src="/.js"></script>
  <script src="/js/next-boot.js"></script>
  <script src="/.js"></script>
  <script>
    (function ()
    {
      var canonicalURL, curProtocol;
      //Get the <link> tag
      var x = document.getElementsByTagName("link");
      //Find the last canonical URL
      if (x.length > 0)
      {
        for (i = 0; i < x.length; i++)
        {
          if (x[i].rel.toLowerCase() == 'canonical' && x[i].href)
          {
            canonicalURL = x[i].href;
          }
        }
      }
      //Get protocol
      if (!canonicalURL)
      {
        curProtocol = window.location.protocol.split(':')[0];
      }
      else
      {
        curProtocol = canonicalURL.split(':')[0];
      }
      //Get current URL if the canonical URL does not exist
      if (!canonicalURL) canonicalURL = window.location.href;
      //Assign script content. Replace current URL with the canonical URL
      ! function ()
      {
        var e = /([http|https]:\/\/[a-zA-Z0-9\_\.]+\.baidu\.com)/gi,
          r = canonicalURL,
          t = document.referrer;
        if (!e.test(r))
        {
          var n = (String(curProtocol).toLowerCase() === 'https') ? "https://sp0.baidu.com/9_Q4simg2RQJ8t7jm9iCKT-xh_/s.gif" : "//api.share.baidu.com/s.gif";
          t ? (n += "?r=" + encodeURIComponent(document.referrer), r && (n += "&l=" + r)) : r && (n += "?l=" + r);
          var i = new Image;
          i.src = n
        }
      }(window);
    })();

  </script>
  <script src="/js/local-search.js"></script>
  <script src="/.js"></script>
</body>

</html>
